diff --git a/packages/core/package.json b/packages/core/package.json index b299871e9..df0a5b790 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -41,6 +41,7 @@ "@embroider/macros": "0.45.0", "@embroider/shared-internals": "0.45.0", "assert-never": "^1.2.1", + "babel-import-util": "^0.2.0", "broccoli-node-api": "^1.7.0", "broccoli-persistent-filter": "^3.1.2", "broccoli-plugin": "^4.0.7", diff --git a/packages/core/src/babel-import-adder.ts b/packages/core/src/babel-import-adder.ts deleted file mode 100644 index d61a4f733..000000000 --- a/packages/core/src/babel-import-adder.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { NodePath } from '@babel/traverse'; -import type * as t from '@babel/types'; - -type BabelTypes = typeof t; - -export class ImportAdder { - constructor(private t: BabelTypes, private program: NodePath) {} - - import(target: NodePath, moduleSpecifier: string, exportedName: string, nameHint?: string): t.Identifier { - let declaration = this.program - .get('body') - .find(elt => elt.isImportDeclaration() && elt.node.source.value === moduleSpecifier) as - | undefined - | NodePath; - if (declaration) { - let specifier = declaration - .get('specifiers') - .find(spec => - exportedName === 'default' - ? spec.isImportDefaultSpecifier() - : spec.isImportSpecifier() && name(spec.node.imported) === exportedName - ) as undefined | NodePath | NodePath; - if (specifier && target.scope.getBinding(specifier.node.local.name)?.kind === 'module') { - return specifier.node.local; - } else { - return this.addSpecifier(target, declaration, exportedName, nameHint); - } - } else { - this.program.node.body.unshift(this.t.importDeclaration([], this.t.stringLiteral(moduleSpecifier))); - return this.addSpecifier( - target, - this.program.get(`body.0`) as NodePath, - exportedName, - nameHint - ); - } - } - - private addSpecifier( - target: NodePath, - declaration: NodePath, - exportedName: string, - nameHint: string | undefined - ): t.Identifier { - let local = this.t.identifier(unusedNameLike(target, desiredName(nameHint, exportedName, target))); - let specifier = - exportedName === 'default' - ? this.t.importDefaultSpecifier(local) - : this.t.importSpecifier(local, this.t.identifier(exportedName)); - declaration.node.specifiers.push(specifier); - declaration.scope.registerBinding( - 'module', - declaration.get(`specifiers.${declaration.node.specifiers.length - 1}`) as NodePath - ); - return local; - } -} - -function unusedNameLike(path: NodePath, name: string): string { - let candidate = name; - let counter = 0; - while (path.scope.hasBinding(candidate)) { - candidate = `${name}${counter++}`; - } - return candidate; -} - -function name(node: t.StringLiteral | t.Identifier): string { - if (node.type === 'StringLiteral') { - return node.value; - } else { - return node.name; - } -} - -function desiredName(nameHint: string | undefined, exportedName: string, target: NodePath) { - if (nameHint) { - return nameHint; - } - if (exportedName === 'default') { - if (target.isIdentifier()) { - return target.node.name; - } else { - return target.scope.generateUidIdentifierBasedOnNode(target.node).name; - } - } else { - return exportedName; - } -} diff --git a/packages/core/src/babel-plugin-inline-hbs.ts b/packages/core/src/babel-plugin-inline-hbs.ts index f114a141e..a8c8e1843 100644 --- a/packages/core/src/babel-plugin-inline-hbs.ts +++ b/packages/core/src/babel-plugin-inline-hbs.ts @@ -4,7 +4,7 @@ import { join } from 'path'; import { TemplateCompiler } from './template-compiler-common'; import { parse } from '@babel/core'; import { ResolvedDep } from './resolver'; -import { ImportAdder } from './babel-import-adder'; +import { ImportUtil } from 'babel-import-util'; type BabelTypes = typeof t; @@ -34,7 +34,7 @@ interface State { }; dependencies: Map; templateCompiler: TemplateCompiler | undefined; - adder: ImportAdder; + adder: ImportUtil; } export type Params = State['opts']; @@ -47,7 +47,7 @@ export default function make(getCompiler: (opts: any) => TemplateCompiler) { Program: { enter(path: NodePath, state: State) { state.dependencies = new Map(); - state.adder = new ImportAdder(t, path); + state.adder = new ImportUtil(t, path); }, exit(path: NodePath, state: State) { if (state.opts.stage === 3) { diff --git a/packages/core/src/template-colocation-plugin.ts b/packages/core/src/template-colocation-plugin.ts index 9994fd456..50fbaecf8 100644 --- a/packages/core/src/template-colocation-plugin.ts +++ b/packages/core/src/template-colocation-plugin.ts @@ -4,7 +4,7 @@ import type * as t from '@babel/types'; import { dirname } from 'path'; import { explicitRelative } from '@embroider/shared-internals'; import { PackageCache } from '@embroider/shared-internals'; -import { ImportAdder } from './babel-import-adder'; +import { ImportUtil } from 'babel-import-util'; type BabelTypes = typeof t; @@ -13,7 +13,7 @@ const packageCache = PackageCache.shared('embroider-stage3'); interface State { colocatedTemplate: string | undefined; associate: { component: t.Identifier; template: t.Identifier } | undefined; - adder: ImportAdder; + adder: ImportUtil; } function setComponentTemplate(target: NodePath, state: State) { @@ -26,7 +26,7 @@ export default function main(babel: unknown) { visitor: { Program: { enter(path: NodePath, state: State) { - state.adder = new ImportAdder(t, path); + state.adder = new ImportUtil(t, path); let filename = path.hub.file.opts.filename; let owningPackage = packageCache.ownerOfFile(filename); @@ -127,6 +127,6 @@ export default function main(babel: unknown) { }; } -function importTemplate(target: NodePath, adder: ImportAdder, colocatedTemplate: string) { +function importTemplate(target: NodePath, adder: ImportUtil, colocatedTemplate: string) { return adder.import(target, explicitRelative(dirname(colocatedTemplate), colocatedTemplate), 'default', 'TEMPLATE'); } diff --git a/packages/core/tests/import-adder.test.ts b/packages/core/tests/import-adder.test.ts deleted file mode 100644 index 33db57d17..000000000 --- a/packages/core/tests/import-adder.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { allBabelVersions, runDefault } from '@embroider/test-support'; -import { ImportAdder } from '../src/babel-import-adder'; -import type { NodePath } from '@babel/traverse'; -import type * as t from '@babel/types'; - -function importAdderTests(transform: (code: string) => string) { - const dependencies = { - m: { - thing(arg: string) { - return `you said: ${arg}.`; - }, - default(arg: string) { - return `default said: ${arg}.`; - }, - __esModule: true, - }, - n: { - thing(arg: string) { - return `n said: ${arg}.`; - }, - __esModule: true, - }, - }; - - test('can generate an import', () => { - let code = transform(` - export default function() { - return myTarget('foo'); - } - `); - expect(runDefault(code, { dependencies })).toEqual('you said: foo.'); - expect(code).toMatch(/import \{ thing \} from ['"]m['"]/); - }); - - test('can generate a default import', () => { - let code = transform(` - export default function() { - return myDefaultTarget('foo'); - } - `); - expect(runDefault(code, { dependencies })).toEqual('default said: foo.'); - expect(code).toMatch(/import myDefaultTarget from ['"]m['"]/); - }); - - test('can use an optional name hint', () => { - let code = transform(` - export default function() { - return myHintTarget('foo'); - } - `); - expect(runDefault(code, { dependencies })).toEqual('default said: foo.'); - expect(code).toMatch(/import HINT from ['"]m['"]/); - }); - - test('avoids an existing local binding', () => { - let code = transform(` - export default function() { - let thing = 'hello'; - return myTarget(thing); - } - `); - expect(runDefault(code, { dependencies })).toEqual('you said: hello.'); - expect(code).toMatch(/import \{ thing as thing0 \} from ['"]m['"]/); - }); - - test('uses an existing import', () => { - let code = transform(` - import { thing } from 'm'; - export default function() { - return myTarget('foo'); - } - `); - expect(runDefault(code, { dependencies })).toEqual('you said: foo.'); - expect(code.match(/import/g)?.length).toEqual(1); - }); - - test('adds to an existing import', () => { - let code = transform(` - import { other } from 'm'; - export default function() { - return myTarget('foo'); - } - `); - expect(runDefault(code, { dependencies })).toEqual('you said: foo.'); - expect(code.match(/import/g)?.length).toEqual(1); - expect(code).toMatch(/import \{ other, thing \} from ['"]m['"]/); - }); - - test('subsequent imports avoid previously created bindings', () => { - let code = transform(` - export default function() { - return myTarget("a") + " | " + second("b"); - } - `); - expect(runDefault(code, { dependencies })).toEqual('you said: a. | n said: b.'); - expect(code).toMatch(/import \{ thing \} from ['"]m['"]/); - expect(code).toMatch(/import \{ thing as thing0 \} from ['"]n['"]/); - }); - - test('multiple uses share an import', () => { - let code = transform(` - export default function() { - return myTarget("a") + " | " + myTarget("b"); - } - `); - expect(runDefault(code, { dependencies })).toEqual('you said: a. | you said: b.'); - expect(code).toMatch(/import \{ thing \} from ['"]m['"]/); - expect(code.match(/import/g)?.length).toEqual(1); - }); - - test('multiple uses in different scopes share a specifier', () => { - let code = transform(` - function a() { - return myTarget('a'); - } - function b() { - return myTarget('b'); - } - export default function() { - return a() + " | " + b(); - } - `); - expect(runDefault(code, { dependencies })).toEqual('you said: a. | you said: b.'); - expect(code).toMatch(/import \{ thing \} from ['"]m['"]/); - expect(code.match(/import/g)?.length).toEqual(1); - }); - - test('resolves conflicts between different local scope collisions', () => { - let code = transform(` - export default function() { - let first = myTarget("a"); - let second = (function(thing) { - return myTarget(thing); - })("b"); - return first + " | " + second; - } - `); - expect(runDefault(code, { dependencies })).toEqual('you said: a. | you said: b.'); - expect(code).toMatch(/import \{ thing, thing as thing0 \} from ['"]m['"]/); - expect(code.match(/import/g)?.length).toEqual(1); - }); -} - -interface State { - adder: ImportAdder; -} - -function testTransform(babel: { types: typeof t }): unknown { - return { - visitor: { - Program: { - enter(path: NodePath, state: State) { - state.adder = new ImportAdder(babel.types, path); - }, - }, - CallExpression(path: NodePath, state: State) { - let callee = path.get('callee'); - if (callee.isIdentifier() && callee.node.name === 'myTarget') { - callee.replaceWith(state.adder.import(callee, 'm', 'thing')); - } else if (callee.isIdentifier() && callee.node.name === 'second') { - callee.replaceWith(state.adder.import(callee, 'n', 'thing')); - } else if (callee.isIdentifier() && callee.node.name === 'myDefaultTarget') { - callee.replaceWith(state.adder.import(callee, 'm', 'default')); - } else if (callee.isIdentifier() && callee.node.name === 'myHintTarget') { - callee.replaceWith(state.adder.import(callee, 'm', 'default', 'HINT')); - } - }, - }, - }; -} - -describe('import-adder', () => { - allBabelVersions({ - babelConfig() { - return { - plugins: [testTransform], - }; - }, - createTests: importAdderTests, - }); -}); diff --git a/yarn.lock b/yarn.lock index a421e6c98..d3d85dea1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4024,6 +4024,11 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" +babel-import-util@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/babel-import-util/-/babel-import-util-0.2.0.tgz#b468bb679919601a3570f9e317536c54f2862e23" + integrity sha512-CtWYYHU/MgK88rxMrLfkD356dApswtR/kWZ/c6JifG1m10e7tBBrs/366dFzWMAoqYmG5/JSh+94tUSpIwh+ag== + babel-jest@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" @@ -5651,7 +5656,15 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^3.2.6, browserslist@^4.0.0, browserslist@^4.14.0, browserslist@^4.14.5, browserslist@^4.16.6: +browserslist@^3.2.6: + version "3.2.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" + integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== + dependencies: + caniuse-lite "^1.0.30000844" + electron-to-chromium "^1.3.47" + +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6: version "4.17.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.0.tgz#1fcd81ec75b41d6d4994fb0831b92ac18c01649c" integrity sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g== @@ -5896,6 +5909,11 @@ caniuse-lite@^1.0.0: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001236.tgz#0a80de4cdf62e1770bb46a30d884fc8d633e3958" integrity sha512-o0PRQSrSCGJKCPZcgMzl5fUaj5xHe8qA2m4QRvnyY4e1lITqoNkr7q/Oh1NcpGSy0Th97UZ35yoKcINPoq7YOQ== +caniuse-lite@^1.0.30000844: + version "1.0.30001263" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001263.tgz#7ce7a6fb482a137585cbc908aaf38e90c53a16a4" + integrity sha512-doiV5dft6yzWO1WwU19kt8Qz8R0/8DgEziz6/9n2FxUasteZNwNNYSmJO3GLBH8lCVE73AB1RPDPAeYbcO5Cvw== + caniuse-lite@^1.0.30001254: version "1.0.30001257" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz#150aaf649a48bee531104cfeda57f92ce587f6e5" @@ -7118,6 +7136,11 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +electron-to-chromium@^1.3.47: + version "1.3.856" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.856.tgz#75dee0eef9702bffabbf4c1293c989cd3cacb7ba" + integrity sha512-lSezYIe1/p5qkEswAfaQUseOBiwGwuCvRl/MKzOEVe++DcmQ92+43dznDl4rFJ4Zpu+kevhwyIf7KjJevyDA/A== + electron-to-chromium@^1.3.830: version "1.3.840" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.840.tgz#3f2a1df97015d9b1db5d86a4c6bd4cdb920adcbb" @@ -9915,7 +9938,21 @@ fastboot-transform@^0.1.0, fastboot-transform@^0.1.3: broccoli-stew "^1.5.0" convert-source-map "^1.5.1" -fastboot@^2.0.0, fastboot@^2.0.1, fastboot@^3.1.0: +fastboot@^2.0.0, fastboot@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fastboot/-/fastboot-2.0.3.tgz#0b712e6c590f1b463dc5b12138893bcbbafa2459" + integrity sha512-NNH/o+XhITAQUnW2CC9IDXlcnI74W2BONjtRSRmc01N3uJl/7pcvX9iWTUWu2PYQbQZUBu8HzVFt7GmQ9qw9JQ== + dependencies: + chalk "^2.0.1" + cookie "^0.4.0" + debug "^4.1.0" + najax "^1.0.3" + resolve "^1.8.1" + rsvp "^4.8.0" + simple-dom "^1.4.0" + source-map-support "^0.5.0" + +fastboot@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/fastboot/-/fastboot-3.1.2.tgz#c10a97be3a61fbbf9e4bd8abc43373e8739d1787" integrity sha512-yvhJfIRd4wWWACk+qjJxQI+WBIQ+pyQyp0/fxrQyA/cYJgZAXOHb+22zXJbJXaPku3fHS+gBl7crwovIkl8bhQ== @@ -12405,6 +12442,11 @@ jest@^24.9.0: import-local "^2.0.0" jest-cli "^24.9.0" +jquery-deferred@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/jquery-deferred/-/jquery-deferred-0.3.1.tgz#596eca1caaff54f61b110962b23cafea74c35355" + integrity sha1-WW7KHKr/VPYbEQlisjyv6nTDU1U= + jquery@^3.4.1, jquery@^3.5.0, jquery@^3.5.1: version "3.6.0" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" @@ -13740,6 +13782,15 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" +najax@^1.0.3: + version "1.0.7" + resolved "https://registry.yarnpkg.com/najax/-/najax-1.0.7.tgz#706dce52d4b738dce01aee97f392ccdb79d51eef" + integrity sha512-JqBMguf2plv1IDqhOE6eebnTivjS/ej0C/Sw831jVc+dRQIMK37oyktdQCGAQtwpl5DikOWI2xGfIlBPSSLgXg== + dependencies: + jquery-deferred "^0.3.0" + lodash "^4.17.21" + qs "^6.2.0" + nan@^2.12.1: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" @@ -14955,7 +15006,7 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.4.0, qs@^6.9.4: +qs@^6.2.0, qs@^6.4.0, qs@^6.9.4: version "6.10.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== @@ -15025,7 +15076,7 @@ qunit-dom@^1.6.0: ember-cli-babel "^7.23.0" ember-cli-version-checker "^5.1.1" -qunit@^2.14.1, qunit@^2.16.0: +qunit@^2.16.0: version "2.17.1" resolved "https://registry.yarnpkg.com/qunit/-/qunit-2.17.1.tgz#1969efe4c9b776b4b8cd4fc2fb9634e8f762e177" integrity sha512-Gx1tpSfYbjRe4TRKCVBLlnCaVThF5Pdnmbbv/zLFfgWKddeQHV/eNi1BG392hw4gEDh2sflMj8kmPJlT7+kVMA== @@ -15729,7 +15780,7 @@ rsvp@^3.0.14, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0 resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw== -rsvp@^4.0.1, rsvp@^4.6.1, rsvp@^4.7.0, rsvp@^4.8.1, rsvp@^4.8.2, rsvp@^4.8.3, rsvp@^4.8.4, rsvp@^4.8.5: +rsvp@^4.0.1, rsvp@^4.6.1, rsvp@^4.7.0, rsvp@^4.8.0, rsvp@^4.8.1, rsvp@^4.8.2, rsvp@^4.8.3, rsvp@^4.8.4, rsvp@^4.8.5: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== @@ -16237,6 +16288,14 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" +source-map-support@^0.5.0: + version "0.5.20" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" + integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.10, source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"