Skip to content

Commit 5140d6d

Browse files
cursoragentn3ps
andcommitted
feat: add ~/ui and ~/shared import alias infrastructure
Configure all build systems to resolve ~/ui and ~/shared aliases: - tsconfig.json: paths for ~/ui/* and ~/shared/* - webpack: resolve.alias entries - browserify: custom babel plugin (development/build/transforms/import-alias.js) - jest: moduleNameMapper in unit and integration configs - storybook: resolve.alias in webpack config - depcheck: ignore alias prefix in dependency check The babel plugin rewrites ~/ui/foo and ~/shared/foo to relative paths during transpilation. No fs access needed, so LavaMoat is unaffected. No existing imports are changed. Both relative and aliased imports work simultaneously, enabling gradual opt-in adoption. Co-authored-by: Francis Nepomuceno <n3ps@users.noreply.github.com>
1 parent 5a2856e commit 5140d6d

File tree

7 files changed

+222
-68
lines changed

7 files changed

+222
-68
lines changed

babel.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = function (api) {
2121
},
2222
],
2323
plugins: [
24+
path.resolve(__dirname, 'development/build/transforms/import-alias.js'),
2425
// `browserify` is old and busted, and doesn't support `??=` (and other
2526
// logical assignment operators). This plugin lets us target es2020-level
2627
// browsers (except we do still end up with transpiled logical assignment

development/build/scripts.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const lavamoatBrowserify = require('lavamoat-browserify');
2727
const terser = require('terser');
2828

2929
const bifyModuleGroups = require('bify-module-groups');
30-
const pathmodify = require('pathmodify');
3130

3231
const { streamFlatMap } = require('../stream-flat-map');
3332
const { isManifestV3 } = require('../../shared/modules/mv3.utils');
@@ -1110,13 +1109,6 @@ async function createBundle(buildConfiguration, { reloadOnChange }) {
11101109
const { label, bundlerOpts, events } = buildConfiguration;
11111110
const bundler = browserify(bundlerOpts);
11121111

1113-
bundler.plugin(pathmodify, {
1114-
mods: [
1115-
pathmodify.mod.dir('~/ui', path.join(__dirname, '../../ui')),
1116-
pathmodify.mod.dir('~/shared', path.join(__dirname, '../../shared')),
1117-
],
1118-
});
1119-
11201112
// manually apply non-standard options
11211113
bundler.external(bundlerOpts.manualExternal);
11221114
bundler.ignore(bundlerOpts.manualIgnore);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
const path = require('path');
2+
3+
const ROOT = path.resolve(__dirname, '../../..');
4+
5+
/**
6+
* Mapping of import alias prefixes to directories (relative to project root).
7+
* Add new aliases here to make them available across the codebase.
8+
*/
9+
const ALIASES = {
10+
'~/ui': 'ui',
11+
'~/shared': 'shared',
12+
};
13+
14+
/**
15+
* If `importSource` starts with a known alias, return the equivalent
16+
* relative path from `filename`'s directory. Otherwise return null.
17+
*
18+
* @param {string} importSource - e.g. '~/shared/constants/network'
19+
* @param {string} filename - absolute path of the file being compiled
20+
* @returns {string | null} rewritten relative path or null
21+
*/
22+
function rewriteAlias(importSource, filename) {
23+
for (const [alias, directory] of Object.entries(ALIASES)) {
24+
if (importSource !== alias && !importSource.startsWith(`${alias}/`)) {
25+
continue;
26+
}
27+
28+
const rest = importSource.slice(alias.length);
29+
const absoluteTarget = path.join(ROOT, directory, rest);
30+
let relativePath = path.relative(path.dirname(filename), absoluteTarget);
31+
32+
if (!relativePath.startsWith('.')) {
33+
relativePath = `./${relativePath}`;
34+
}
35+
36+
return relativePath;
37+
}
38+
39+
return null;
40+
}
41+
42+
/**
43+
* Babel plugin that rewrites `~/ui/...` and `~/shared/...` import aliases
44+
* to relative paths. This allows browserify (which has no native alias
45+
* support) to resolve them using standard Node module resolution.
46+
*
47+
* @returns {import('@babel/core').PluginObj} Babel plugin object
48+
*/
49+
module.exports = function importAliasPlugin() {
50+
return {
51+
visitor: {
52+
// import X from '~/shared/...'
53+
// export { X } from '~/shared/...'
54+
// export * from '~/shared/...'
55+
'ImportDeclaration|ExportNamedDeclaration|ExportAllDeclaration'(
56+
nodePath,
57+
state,
58+
) {
59+
const { source } = nodePath.node;
60+
if (!source) {
61+
return;
62+
}
63+
64+
const rewritten = rewriteAlias(source.value, state.filename);
65+
if (rewritten) {
66+
source.value = rewritten;
67+
}
68+
},
69+
70+
// require('~/shared/...')
71+
CallExpression(nodePath, state) {
72+
const { callee } = nodePath.node;
73+
const arg = nodePath.node.arguments[0];
74+
75+
if (
76+
callee.type !== 'Identifier' ||
77+
callee.name !== 'require' ||
78+
!arg ||
79+
arg.type !== 'StringLiteral'
80+
) {
81+
return;
82+
}
83+
84+
const rewritten = rewriteAlias(arg.value, state.filename);
85+
if (rewritten) {
86+
arg.value = rewritten;
87+
}
88+
},
89+
},
90+
};
91+
};
92+
93+
module.exports.rewriteAlias = rewriteAlias;
94+
module.exports.ALIASES = ALIASES;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
const path = require('path');
2+
const { transformSync } = require('@babel/core');
3+
const { rewriteAlias } = require('./import-alias');
4+
5+
const ROOT = path.resolve(__dirname, '../../..');
6+
7+
describe('import-alias babel plugin', () => {
8+
function transform(code, filePath) {
9+
const result = transformSync(code, {
10+
filename: path.join(ROOT, filePath),
11+
plugins: [require.resolve('./import-alias')],
12+
parserOpts: { plugins: ['typescript'] },
13+
configFile: false,
14+
babelrc: false,
15+
});
16+
return result.code;
17+
}
18+
19+
describe('rewriteAlias', () => {
20+
it('rewrites ~/shared/ to a relative path', () => {
21+
const filename = path.join(ROOT, 'app/scripts/migrations/183.ts');
22+
const result = rewriteAlias('~/shared/constants/network', filename);
23+
expect(result).toBe('../../../shared/constants/network');
24+
});
25+
26+
it('rewrites ~/ui/ to a relative path', () => {
27+
const filename = path.join(
28+
ROOT,
29+
'ui/components/multichain/activity-v2/hooks.ts',
30+
);
31+
const result = rewriteAlias('~/ui/hooks/useI18nContext', filename);
32+
expect(result).toBe('../../../hooks/useI18nContext');
33+
});
34+
35+
it('returns null for non-alias imports', () => {
36+
const filename = path.join(ROOT, 'ui/components/foo.ts');
37+
expect(rewriteAlias('./helpers', filename)).toBeNull();
38+
expect(rewriteAlias('react', filename)).toBeNull();
39+
expect(rewriteAlias('@metamask/utils', filename)).toBeNull();
40+
});
41+
42+
it('does not match partial prefix like ~/shared-extra', () => {
43+
const filename = path.join(ROOT, 'ui/components/foo.ts');
44+
expect(rewriteAlias('~/shared-extra/foo', filename)).toBeNull();
45+
});
46+
47+
it('handles bare alias without subpath', () => {
48+
const filename = path.join(ROOT, 'app/scripts/background.js');
49+
const result = rewriteAlias('~/shared', filename);
50+
expect(result).toBe('../../shared');
51+
});
52+
});
53+
54+
describe('babel transform: import declarations', () => {
55+
it('rewrites named import from ~/shared/', () => {
56+
const code = `import { CHAIN_IDS } from '~/shared/constants/network';`;
57+
const output = transform(
58+
code,
59+
'ui/components/multichain/activity-v2/hooks.ts',
60+
);
61+
expect(output).toContain(
62+
`from "../../../../shared/constants/network"`,
63+
);
64+
expect(output).not.toContain('~/');
65+
});
66+
67+
it('rewrites default import from ~/ui/', () => {
68+
const code = `import AssetPage from '~/ui/pages/asset/components/asset-page';`;
69+
const output = transform(code, 'ui/components/multichain/foo.ts');
70+
expect(output).toContain(`from "../../pages/asset/components/asset-page"`);
71+
expect(output).not.toContain('~/');
72+
});
73+
74+
it('rewrites type import from ~/shared/', () => {
75+
const code = `import type { Token } from '~/shared/lib/multichain/types';`;
76+
const output = transform(
77+
code,
78+
'ui/components/multichain/activity-v2/hooks.ts',
79+
);
80+
expect(output).toContain(`"../../../../shared/lib/multichain/types"`);
81+
});
82+
83+
it('does not touch non-alias imports', () => {
84+
const code = [
85+
`import React from 'react';`,
86+
`import { Box } from '@metamask/design-system-react';`,
87+
`import { foo } from './helpers';`,
88+
`import { bar } from '../utils';`,
89+
].join('\n');
90+
const output = transform(code, 'ui/components/foo.ts');
91+
expect(output).toContain(`react`);
92+
expect(output).toContain(`@metamask/design-system-react`);
93+
expect(output).toContain(`./helpers`);
94+
expect(output).toContain(`../utils`);
95+
expect(output).not.toContain('~/');
96+
});
97+
});
98+
99+
describe('babel transform: export declarations', () => {
100+
it('rewrites export { X } from ~/shared/', () => {
101+
const code = `export { CHAIN_IDS } from '~/shared/constants/network';`;
102+
const output = transform(code, 'app/scripts/lib/util.ts');
103+
expect(output).toContain(`from "../../../shared/constants/network"`);
104+
});
105+
106+
it('rewrites export * from ~/ui/', () => {
107+
const code = `export * from '~/ui/selectors';`;
108+
const output = transform(code, 'ui/components/foo.ts');
109+
expect(output).toContain(`from "../selectors"`);
110+
});
111+
});
112+
113+
describe('babel transform: require calls', () => {
114+
it('rewrites require(~/shared/)', () => {
115+
const code = `const { foo } = require('~/shared/lib/sentry');`;
116+
const output = transform(code, 'app/scripts/migrations/183.ts');
117+
expect(output).toContain(`require("../../../shared/lib/sentry")`);
118+
});
119+
120+
it('does not touch non-alias require calls', () => {
121+
const code = `const path = require('path');`;
122+
const output = transform(code, 'app/scripts/lib/util.ts');
123+
expect(output).toContain(`require('path')`);
124+
});
125+
});
126+
});

lavamoat/build-system/policy.json

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5017,14 +5017,6 @@
50175017
"util.promisify": true
50185018
}
50195019
},
5020-
"pathmodify": {
5021-
"builtin": {
5022-
"util.deprecate": true
5023-
},
5024-
"packages": {
5025-
"pathmodify>readable-stream": true
5026-
}
5027-
},
50285020
"gulp-livereload>event-stream>pause-stream": {
50295021
"packages": {
50305022
"browserify>JSONStream>through": true
@@ -5639,27 +5631,6 @@
56395631
"@storybook/react>util-deprecate": true
56405632
}
56415633
},
5642-
"pathmodify>readable-stream": {
5643-
"builtin": {
5644-
"buffer.Buffer": true,
5645-
"events.EventEmitter.listenerCount": true,
5646-
"stream": true,
5647-
"util": true
5648-
},
5649-
"globals": {
5650-
"process.browser": true,
5651-
"process.env.READABLE_STREAM": true,
5652-
"process.nextTick": true,
5653-
"process.stderr": true,
5654-
"process.stdout": true
5655-
},
5656-
"packages": {
5657-
"readable-stream-2>core-util-is": true,
5658-
"pumpify>inherits": true,
5659-
"pathmodify>readable-stream>isarray": true,
5660-
"pathmodify>readable-stream>string_decoder": true
5661-
}
5662-
},
56635634
"browserify>read-only-stream>readable-stream": {
56645635
"builtin": {
56655636
"events.EventEmitter": true,
@@ -7051,11 +7022,6 @@
70517022
"gulp>vinyl-fs>glob-stream>ordered-read-streams>readable-stream>safe-buffer": true
70527023
}
70537024
},
7054-
"pathmodify>readable-stream>string_decoder": {
7055-
"builtin": {
7056-
"buffer.Buffer": true
7057-
}
7058-
},
70597025
"browserify>read-only-stream>readable-stream>string_decoder": {
70607026
"packages": {
70617027
"browserify>read-only-stream>readable-stream>safe-buffer": true

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,6 @@
693693
"nyc": "^15.1.0",
694694
"octokit": "^4.1.3",
695695
"path-browserify": "^1.0.1",
696-
"pathmodify": "^0.5.0",
697696
"postcss": "^8.4.32",
698697
"postcss-discard-font-face": "^3.0.0",
699698
"postcss-loader": "^8.1.1",

yarn.lock

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33258,7 +33258,6 @@ __metadata:
3325833258
nyc: "npm:^15.1.0"
3325933259
octokit: "npm:^4.1.3"
3326033260
path-browserify: "npm:^1.0.1"
33261-
pathmodify: "npm:^0.5.0"
3326233261
pify: "npm:^5.0.0"
3326333262
postcss: "npm:^8.4.32"
3326433263
postcss-discard-font-face: "npm:^3.0.0"
@@ -35790,17 +35789,6 @@ __metadata:
3579035789
languageName: node
3579135790
linkType: hard
3579235791

35793-
"pathmodify@npm:^0.5.0":
35794-
version: 0.5.0
35795-
resolution: "pathmodify@npm:0.5.0"
35796-
dependencies:
35797-
readable-stream: "npm:1.x"
35798-
peerDependencies:
35799-
browserify: ">=5.1.0"
35800-
checksum: 10/48cc6345fc534c2d37cf8ad191c7f28663262c71877e62609570d32d3caf2655d1837b10ff90994bc731f581737d486eafeba1ef605627d67b00d1d967ab87ac
35801-
languageName: node
35802-
linkType: hard
35803-
3580435792
"pause-stream@npm:0.0.11":
3580535793
version: 0.0.11
3580635794
resolution: "pause-stream@npm:0.0.11"
@@ -38053,18 +38041,6 @@ __metadata:
3805338041
languageName: node
3805438042
linkType: hard
3805538043

38056-
"readable-stream@npm:1.x":
38057-
version: 1.1.14
38058-
resolution: "readable-stream@npm:1.1.14"
38059-
dependencies:
38060-
core-util-is: "npm:~1.0.0"
38061-
inherits: "npm:~2.0.1"
38062-
isarray: "npm:0.0.1"
38063-
string_decoder: "npm:~0.10.x"
38064-
checksum: 10/1aa2cf4bd02f9ab3e1d57842a43a413b52be5300aa089ad1f2e3cea00684532d73edc6a2ba52b0c3210d8b57eb20a695a6d2b96d1c6085ee979c6021ad48ad20
38065-
languageName: node
38066-
linkType: hard
38067-
3806838044
"readable-stream@npm:4.7.0, readable-stream@npm:^3.6.2 || ^4.4.2, readable-stream@npm:^4.5.2, readable-stream@npm:^4.7.0":
3806938045
version: 4.7.0
3807038046
resolution: "readable-stream@npm:4.7.0"
@@ -41138,7 +41114,7 @@ __metadata:
4113841114
languageName: node
4113941115
linkType: hard
4114041116

41141-
"string_decoder@npm:0.10, string_decoder@npm:~0.10.x":
41117+
"string_decoder@npm:0.10":
4114241118
version: 0.10.31
4114341119
resolution: "string_decoder@npm:0.10.31"
4114441120
checksum: 10/cc43e6b1340d4c7843da0e37d4c87a4084c2342fc99dcf6563c3ec273bb082f0cbd4ebf25d5da19b04fb16400d393885fda830be5128e1c416c73b5a6165f175

0 commit comments

Comments
 (0)