Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add non-es6-compat to importSync #1076

Merged
merged 5 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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