Skip to content

Commit

Permalink
Merge pull request #1076 from embroider-build/import-sync-compat
Browse files Browse the repository at this point in the history
add non-es6-compat to importSync
  • Loading branch information
ef4 committed Jan 14, 2022
2 parents 42baeb0 + 5cb2226 commit 0201b62
Show file tree
Hide file tree
Showing 15 changed files with 86 additions and 152 deletions.
6 changes: 3 additions & 3 deletions packages/compat/tests/stage2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,10 +461,10 @@ describe('stage2 build', function () {
test('addon/hello-world.js', function () {
let assertFile = expectFile('node_modules/my-addon/components/hello-world.js').transform(build.transpile);
assertFile.matches(
/window\.define\(["']\my-addon\/synthetic-import-1["'],\s*function\s\(\)\s*\{\s*return\s+require\(["']\.\.\/synthetic-import-1/
/window\.define\(["']\my-addon\/synthetic-import-1["'],\s*function\s\(\)\s*\{\s*return\s+esc\(require\(["']\.\.\/synthetic-import-1/
);
assertFile.matches(
/window\.define\(["']my-app\/templates\/components\/second-choice["'],\s*function\s\(\)\s*\{\s*return\s+require\(["']\.\.\/\.\.\/\.\.\/templates\/components\/second-choice\.hbs["']/
/window\.define\(["']my-app\/templates\/components\/second-choice["'],\s*function\s\(\)\s*\{\s*return\s+esc\(require\(["']\.\.\/\.\.\/\.\.\/templates\/components\/second-choice\.hbs["']/
);
assertFile.matches(
/import somethingExternal from ["'].*\/externals\/not-a-resolvable-package["']/,
Expand All @@ -475,7 +475,7 @@ describe('stage2 build', function () {
test('app/hello-world.js', function () {
let assertFile = expectFile('./components/hello-world.js').transform(build.transpile);
assertFile.matches(
/window\.define\(["']\my-addon\/synthetic-import-1["'],\s*function\s\(\)\s*\{\s*return\s+require\(["']\.\.\/node_modules\/my-addon\/synthetic-import-1/
/window\.define\(["']\my-addon\/synthetic-import-1["'],\s*function\s\(\)\s*\{\s*return\s+esc\(require\(["']\.\.\/node_modules\/my-addon\/synthetic-import-1/
);
assertFile.matches(
/export \{ default \} from ['"]\.\.\/node_modules\/my-addon\/components\/hello-world['"]/,
Expand Down
1 change: 1 addition & 0 deletions packages/macros/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"dependencies": {
"@embroider/shared-internals": "0.50.1",
"assert-never": "^1.2.1",
"babel-import-util": "^1.1.0",
"ember-cli-babel": "^7.26.6",
"find-up": "^5.0.0",
"lodash": "^4.17.21",
Expand Down
3 changes: 3 additions & 0 deletions packages/macros/src/addon/es-compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function esCompat(m) {
return m?.__esModule ? m : { default: m };
}
5 changes: 2 additions & 3 deletions packages/macros/src/babel/dependency-satisfies.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NodePath } from '@babel/traverse';
import type { types as t } from '@babel/core';
import State, { sourceFile } from './state';
import State from './state';
import { satisfies } from 'semver';
import error from './error';
import { assertArray } from './evaluate-json';
Expand All @@ -22,9 +22,8 @@ export default function dependencySatisfies(path: NodePath<t.CallExpression>, st
`the second argument to dependencySatisfies must be a string literal`
);
}
let sourceFileName = sourceFile(path, state);
try {
let us = state.packageCache.ownerOfFile(sourceFileName);
let us = state.packageCache.ownerOfFile(state.sourceFile);
if (!us?.hasDependency(packageName.value)) {
return false;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/macros/src/babel/each.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { NodePath } from '@babel/traverse';
import { buildLiterals, Evaluator } from './evaluate-json';
import type { types as t } from '@babel/core';
import error from './error';
import State, { cloneDeep } from './state';
import State from './state';
import type * as Babel from '@babel/core';

type CallEachExpression = NodePath<t.CallExpression> & {
Expand Down Expand Up @@ -51,14 +51,14 @@ export function insertEach(path: EachPath, state: State, context: typeof Babel)

if (state.opts.mode === 'run-time') {
let callee = path.get('right').get('callee');
state.neededRuntimeImports.set(callee.node.name, 'each');
callee.replaceWith(state.importUtil.import(callee, state.pathToOurAddon('runtime'), 'each'));
} else {
for (let element of array.value) {
let literalElement = buildLiterals(element, context);
for (let target of nameRefs) {
target.replaceWith(literalElement);
}
path.insertBefore(cloneDeep(path.get('body').node, state));
path.insertBefore(state.cloneDeep(path.get('body').node));
}
path.remove();
}
Expand Down
10 changes: 6 additions & 4 deletions packages/macros/src/babel/evaluate-json.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NodePath } from '@babel/traverse';
import type * as Babel from '@babel/core';
import type { types as t } from '@babel/core';
import State, { owningPackage } from './state';
import State from './state';
import dependencySatisfies from './dependency-satisfies';
import moduleExists from './module-exists';
import getConfig from './get-config';
Expand Down Expand Up @@ -339,11 +339,13 @@ export class Evaluator {
// to runtime. That's why we've made `value` lazy. It lets us check the
// confidence without actually forcing the value.
private maybeEvaluateRuntimeConfig(path: NodePath<t.CallExpression>): EvaluateResult {
if (!this.state) {
return { confident: false };
}
let callee = path.get('callee');
if (callee.isIdentifier()) {
let { name } = callee.node;
// Does the identifier refer to our runtime config?
if (this.state?.neededRuntimeImports.get(name) === 'config') {
if (callee.referencesImport(this.state.pathToOurAddon('runtime'), 'config')) {
return {
confident: true,
get value() {
Expand Down Expand Up @@ -387,7 +389,7 @@ export class Evaluator {
if (callee.referencesImport('@embroider/macros', 'isDevelopingThisPackage')) {
return {
confident: true,
value: this.state.opts.isDevelopingPackageRoots.includes(owningPackage(path, this.state).root),
value: this.state.opts.isDevelopingPackageRoots.includes(this.state.owningPackage().root),
};
}
if (callee.referencesImport('@embroider/macros', 'isTesting')) {
Expand Down
23 changes: 9 additions & 14 deletions packages/macros/src/babel/get-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NodePath } from '@babel/traverse';
import State, { sourceFile, unusedNameLike } from './state';
import State from './state';
import { PackageCache, Package } from '@embroider/shared-internals';
import error from './error';
import { Evaluator, assertArray, buildLiterals, ConfidentResult } from './evaluate-json';
Expand Down Expand Up @@ -28,7 +28,7 @@ function getPackage(path: NodePath<t.CallExpression>, state: State, mode: 'own'
} else {
assertNever(mode);
}
return targetPackage(sourceFile(path, state), packageName, state.packageCache);
return targetPackage(state.sourceFile, packageName, state.packageCache);
}

// this evaluates to the actual value of the config. It can be used directly by the Evaluator.
Expand All @@ -55,7 +55,8 @@ export function insertConfig(path: NodePath<t.CallExpression>, state: State, mod
collapsed.path.replaceWith(literalResult);
} else {
if (mode === 'getGlobalConfig') {
state.neededRuntimeImports.set(calleeName(path, context), 'getGlobalConfig');
let callee = path.get('callee');
callee.replaceWith(state.importUtil.import(callee, state.pathToOurAddon('runtime'), 'getGlobalConfig'));
} else {
let pkg = getPackage(path, state, mode);
let pkgRoot;
Expand All @@ -64,9 +65,11 @@ export function insertConfig(path: NodePath<t.CallExpression>, state: State, mod
} else {
pkgRoot = context.types.identifier('undefined');
}
let name = unusedNameLike('config', path);
path.replaceWith(context.types.callExpression(context.types.identifier(name), [pkgRoot]));
state.neededRuntimeImports.set(name, 'config');
path.replaceWith(
context.types.callExpression(state.importUtil.import(path, state.pathToOurAddon('runtime'), 'config'), [
pkgRoot,
])
);
}
}
}
Expand Down Expand Up @@ -106,11 +109,3 @@ export function inlineRuntimeConfig(path: NodePath<t.FunctionDeclaration>, state
),
];
}

function calleeName(path: NodePath<t.CallExpression>, context: typeof Babel): string {
let callee = path.node.callee;
if (context.types.isIdentifier(callee)) {
return callee.name;
}
throw new Error(`bug: our macros should only be invoked as identifiers`);
}
2 changes: 1 addition & 1 deletion packages/macros/src/babel/macro-condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function macroCondition(conditionalPath: MacroConditionPath, stat

if (state.opts.mode === 'run-time') {
let callee = conditionalPath.get('test').get('callee');
state.neededRuntimeImports.set(callee.node.name, 'macroCondition');
callee.replaceWith(state.importUtil.import(callee, state.pathToOurAddon('runtime'), 'macroCondition'));
} else {
let [kept, removed] = predicate.value ? [consequent.node, alternate.node] : [alternate.node, consequent.node];
if (kept) {
Expand Down
94 changes: 16 additions & 78 deletions packages/macros/src/babel/macros-babel-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NodePath } from '@babel/traverse';
import type { types as t } from '@babel/core';
import { PackageCache } from '@embroider/shared-internals';
import State, { sourceFile, pathToRuntime } from './state';
import State, { initState } from './state';
import { inlineRuntimeConfig, insertConfig, Mode as GetConfigMode } from './get-config';
import macroCondition, { isMacroConditionPath } from './macro-condition';
import { isEachPath, insertEach } from './each';
Expand All @@ -15,19 +15,14 @@ export default function main(context: typeof Babel): unknown {
let t = context.types;
let visitor = {
Program: {
enter(_: NodePath<t.Program>, state: State) {
state.generatedRequires = new Set();
state.jobs = [];
state.removed = new Set();
state.calledIdentifiers = new Set();
state.neededRuntimeImports = new Map();
state.neededEagerImports = new Map();
enter(path: NodePath<t.Program>, state: State) {
initState(t, path, state);

state.packageCache = PackageCache.shared('embroider-stage3', state.opts.appPackageRoot);
},
exit(path: NodePath<t.Program>, state: State) {
pruneMacroImports(path);
addRuntimeImports(path, state, context);
addEagerImports(path, state, t);
exit(_: NodePath<t.Program>, state: State) {
// @embroider/macros itself has no runtime behaviors and should always be removed
state.importUtil.removeAllImports('@embroider/macros');
for (let handler of state.jobs) {
handler();
}
Expand All @@ -53,7 +48,7 @@ export default function main(context: typeof Babel): unknown {
enter(path: NodePath<t.FunctionDeclaration>, state: State) {
let id = path.get('id');
if (id.isIdentifier() && id.node.name === 'initializeRuntimeMacrosConfig') {
let pkg = state.packageCache.ownerOfFile(sourceFile(path, state));
let pkg = state.owningPackage();
if (pkg && pkg.name === '@embroider/macros') {
inlineRuntimeConfig(path, state, context);
}
Expand Down Expand Up @@ -106,7 +101,7 @@ export default function main(context: typeof Babel): unknown {
// instead falls through to evaluateMacroCall.
if (callee.referencesImport('@embroider/macros', 'isTesting') && state.opts.mode === 'run-time') {
state.calledIdentifiers.add(callee.node);
state.neededRuntimeImports.set(callee.node.name, 'isTesting');
callee.replaceWith(state.importUtil.import(callee, state.pathToOurAddon('runtime'), 'isTesting'));
return;
}

Expand All @@ -132,17 +127,16 @@ export default function main(context: typeof Babel): unknown {
if (specifier?.type !== 'StringLiteral') {
throw new Error(`importSync eager mode doesn't implement non string literal arguments yet`);
}
let replacePaths = state.neededEagerImports.get(specifier.value);
if (!replacePaths) {
replacePaths = [];
state.neededEagerImports.set(specifier.value, replacePaths);
}
replacePaths.push(path);
path.replaceWith(state.importUtil.import(path, specifier.value, '*'));
state.calledIdentifiers.add(callee.node);
} else {
let r = t.identifier('require');
state.generatedRequires.add(r);
callee.replaceWith(r);
path.replaceWith(
t.callExpression(state.importUtil.import(path, state.pathToOurAddon('es-compat'), 'default', 'esc'), [
t.callExpression(r, path.node.arguments),
])
);
}
return;
}
Expand Down Expand Up @@ -195,7 +189,7 @@ export default function main(context: typeof Babel): unknown {
path.node.name === 'require' &&
!state.generatedRequires.has(path.node) &&
!path.scope.hasBinding('require') &&
ownedByEmberPackage(path, state)
state.owningPackage().isEmberPackage()
) {
// Our importSync macro has been compiled to `require`. But we want to
// distinguish that from any pre-existing, user-written `require` in an
Expand Down Expand Up @@ -223,59 +217,3 @@ export default function main(context: typeof Babel): unknown {

return { visitor };
}

// This removes imports from "@embroider/macros" itself, because we have no
// runtime behavior at all.
function pruneMacroImports(path: NodePath) {
if (!path.isProgram()) {
return;
}
for (let topLevelPath of path.get('body')) {
if (topLevelPath.isImportDeclaration() && topLevelPath.get('source').node.value === '@embroider/macros') {
topLevelPath.remove();
}
}
}

function addRuntimeImports(path: NodePath<t.Program>, state: State, context: typeof Babel) {
let t = context.types;
if (state.neededRuntimeImports.size > 0) {
path.node.body.push(
t.importDeclaration(
[...state.neededRuntimeImports].map(([local, imported]) =>
t.importSpecifier(t.identifier(local), t.identifier(imported))
),
t.stringLiteral(pathToRuntime(path, state))
)
);
}
}

function addEagerImports(path: NodePath<t.Program>, state: State, t: typeof Babel['types']) {
let createdNames = new Set<string>();
for (let [specifier, replacePaths] of state.neededEagerImports.entries()) {
let local = unusedNameLike('a', replacePaths, createdNames);
createdNames.add(local);
path.node.body.push(
t.importDeclaration([t.importNamespaceSpecifier(t.identifier(local))], t.stringLiteral(specifier))
);
for (let nodePath of replacePaths) {
nodePath.replaceWith(t.identifier(local));
}
}
}

function ownedByEmberPackage(path: NodePath, state: State) {
let filename = sourceFile(path, state);
let pkg = state.packageCache.ownerOfFile(filename);
return pkg && pkg.isEmberPackage();
}

function unusedNameLike(name: string, paths: NodePath<unknown>[], banned: Set<string>) {
let candidate = name;
let counter = 0;
while (banned.has(candidate) || paths.some(path => path.scope.getBinding(candidate))) {
candidate = `${name}${counter++}`;
}
return candidate;
}
5 changes: 2 additions & 3 deletions packages/macros/src/babel/module-exists.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NodePath } from '@babel/traverse';
import type { types as t } from '@babel/core';
import State, { sourceFile } from './state';
import State from './state';
import error from './error';
import { assertArray } from './evaluate-json';
import resolve from 'resolve';
Expand All @@ -14,9 +14,8 @@ export default function moduleExists(path: NodePath<t.CallExpression>, state: St
if (moduleSpecifier.type !== 'StringLiteral') {
throw error(assertArray(path.get('arguments'))[0], `the first argument to moduleExists must be a string literal`);
}
let sourceFileName = sourceFile(path, state);
try {
resolve.sync(moduleSpecifier.value, { basedir: dirname(sourceFileName) });
resolve.sync(moduleSpecifier.value, { basedir: dirname(state.sourceFile) });
return true;
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
Expand Down
Loading

0 comments on commit 0201b62

Please sign in to comment.