UNPKG

@embroider/macros

Version:

Standardized build-time macros for ember apps.

308 lines • 16.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = main; const state_1 = require("./state"); const get_config_1 = require("./get-config"); const macro_condition_1 = __importStar(require("./macro-condition")); const each_1 = require("./each"); const error_1 = __importDefault(require("./error")); const fail_build_1 = __importDefault(require("./fail-build")); const evaluate_json_1 = require("./evaluate-json"); const fs_1 = require("fs"); const path_1 = require("path"); function main(context) { let t = context.types; let visitor = { Program: { enter(path, state) { (0, state_1.initState)(context, path, state); }, exit(_, 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(); } }, }, 'IfStatement|ConditionalExpression': { enter(path, state) { let found = (0, macro_condition_1.identifyMacroConditionPath)(path); if (found) { state.calledIdentifiers.add(found.callExpression.get('callee').node); (0, macro_condition_1.default)(found, state); } }, }, ForOfStatement: { enter(path, state) { if ((0, each_1.isEachPath)(path)) { state.calledIdentifiers.add(path.get('right').get('callee').node); (0, each_1.insertEach)(path, state, context); } }, }, FunctionDeclaration: { enter(path, state) { let id = path.get('id'); if (id.isIdentifier() && id.node.name === 'initializeRuntimeMacrosConfig' && state.opts.mode === 'run-time') { let pkg = state.owningPackage(); if (pkg && pkg.name === '@embroider/macros') { (0, get_config_1.inlineRuntimeConfig)(path, state, context); } } }, }, CallExpression: { enter(path, state) { let callee = path.get('callee'); if (!callee.isIdentifier()) { return; } // failBuild is implemented for side-effect, not value, so it's not // handled by evaluateMacroCall. if (callee.referencesImport('@embroider/macros', 'failBuild')) { state.calledIdentifiers.add(callee.node); (0, fail_build_1.default)(path, state); return; } if (callee.referencesImport('@embroider/macros', 'importSync')) { // we handle importSync in the exit hook return; } // getOwnConfig/getGlobalConfig/getConfig needs special handling, so // even though it also emits values via evaluateMacroCall when they're // needed recursively by other macros, it has its own insertion-handling // code that we invoke here. // // The things that are special include: // - automatic collapsing of chained properties, etc // - these macros have runtime implementations sometimes, which changes // how we rewrite them let mode = callee.referencesImport('@embroider/macros', 'getOwnConfig') ? 'own' : callee.referencesImport('@embroider/macros', 'getGlobalConfig') ? 'getGlobalConfig' : callee.referencesImport('@embroider/macros', 'getConfig') ? 'package' : false; if (mode) { state.calledIdentifiers.add(callee.node); (0, get_config_1.insertConfig)(path, state, mode, context); return; } // isTesting can have a runtime implementation. At compile time it // instead falls through to evaluateMacroCall. if (callee.referencesImport('@embroider/macros', 'isTesting') && state.opts.mode === 'run-time') { state.calledIdentifiers.add(callee.node); callee.replaceWith(state.importUtil.import(callee, state.pathToOurAddon('runtime'), 'isTesting')); return; } if (callee.referencesImport('@embroider/macros', 'setTesting')) { state.calledIdentifiers.add(callee.node); if (state.opts.mode === 'run-time') { callee.replaceWith(state.importUtil.import(callee, state.pathToOurAddon('runtime'), 'setTesting')); } else { let args = path.get('arguments'); if (args.length === 0) { throw (0, error_1.default)(path, `setTesting() requires a boolean argument`); } let arg = args[0]; let evaluator = new evaluate_json_1.Evaluator({ state }); let result = evaluator.evaluate(arg); if (!result.confident) { throw (0, error_1.default)(arg, `setTesting() can only be called with a statically analyzable value in compile-time mode. The argument must be a literal boolean or other macro that can resolves to a boolean at compile-time.`); } let macrosConfig = state.opts.globalConfig['@embroider/macros'] || {}; let currentIsTesting = Boolean(macrosConfig.isTesting); let newIsTesting = Boolean(result.value); if (currentIsTesting !== newIsTesting) { throw (0, error_1.default)(path, `setTesting(${newIsTesting}) cannot change the testing state in compile-time mode. The current global config has isTesting=${currentIsTesting}. ` + `setTesting() calls are compiled away at build time, so they cannot change the testing state. ` + `If you need to change the testing state at runtime, use runtime mode instead.`); } path.remove(); } return; } let result = new evaluate_json_1.Evaluator({ state }).evaluateMacroCall(path); if (result.confident) { state.calledIdentifiers.add(callee.node); path.replaceWith((0, evaluate_json_1.buildLiterals)(result.value, context)); } }, exit(path, state) { let callee = path.get('callee'); if (!callee.isIdentifier()) { return; } // importSync doesn't evaluate to a static value, so it's implemented // directly here, not in evaluateMacroCall. // We intentionally do this on exit here, to allow other transforms to handle importSync before we do // For example ember-auto-import needs to do some custom transforms to enable use of dynamic template strings, // so its babel plugin needs to see and handle the importSync call first! if (callee.referencesImport('@embroider/macros', 'importSync')) { if (state.opts.importSyncImplementation === 'eager') { let specifier = path.node.arguments[0]; if ((specifier === null || specifier === void 0 ? void 0 : specifier.type) !== 'StringLiteral') { let relativePath = ''; let property; if (specifier.type === 'TemplateLiteral') { relativePath = specifier.quasis[0].value.cooked; property = specifier.expressions[0]; } // babel might transform template form `../my-path/${id}` to '../my-path/'.concat(id) if (specifier.type === 'CallExpression' && specifier.callee.type === 'MemberExpression' && specifier.callee.property.type === 'Identifier' && specifier.callee.property.name === 'concat' && specifier.callee.object.type === 'StringLiteral') { relativePath = specifier.callee.object.value; property = specifier.arguments[0]; } if (property && relativePath && relativePath.startsWith('.')) { const resolvedPath = (0, path_1.resolve)((0, path_1.dirname)(state.filename), relativePath); let entries = []; if ((0, fs_1.existsSync)(resolvedPath)) { entries = (0, fs_1.readdirSync)(resolvedPath).filter(e => !e.startsWith('.')); } const obj = t.objectExpression(entries.map(e => { let key = e.split('.')[0]; const rest = e.split('.').slice(1, -1); if (rest.length) { key += `.${rest}`; } const id = t.callExpression(state.importUtil.import(path, state.pathToOurAddon('es-compat2'), 'default', 'esc'), [state.importUtil.import(path, (0, path_1.join)(relativePath, key).replace(/\\/g, '/'), '*')]); return t.objectProperty(t.stringLiteral(key), id); })); const memberExpr = t.memberExpression(obj, property, true); path.replaceWith(memberExpr); state.calledIdentifiers.add(callee.node); return; } else { throw new Error(`importSync eager mode only supports dynamic paths which are relative, must start with a '.', had ${specifier.type}`); } } path.replaceWith(t.callExpression(state.importUtil.import(path, state.pathToOurAddon('es-compat2'), 'default', 'esc'), [ state.importUtil.import(path, specifier.value, '*'), ])); state.calledIdentifiers.add(callee.node); return; } else { if (path.scope.hasBinding('require')) { path.scope.rename('require'); } let r = t.identifier('require'); state.generatedRequires.add(r); path.replaceWith(t.callExpression(state.importUtil.import(path, state.pathToOurAddon('es-compat2'), 'default', 'esc'), [ t.callExpression(r, path.node.arguments), ])); } } }, }, ReferencedIdentifier(path, state) { for (let candidate of [ 'dependencySatisfies', 'appEmberSatisfies', 'moduleExists', 'getConfig', 'getOwnConfig', 'failBuild', // we cannot check importSync, as the babel transform runs on exit, so *after* this check // 'importSync', 'isDevelopingApp', 'isDevelopingThisPackage', 'isTesting', 'setTesting', ]) { if (path.referencesImport('@embroider/macros', candidate) && !state.calledIdentifiers.has(path.node)) { throw (0, error_1.default)(path, `You can only use ${candidate} as a function call`); } } if (path.referencesImport('@embroider/macros', 'macroCondition') && !state.calledIdentifiers.has(path.node)) { throw (0, error_1.default)(path, `macroCondition can only be used as the predicate of an if statement or ternary expression`); } if (path.referencesImport('@embroider/macros', 'each') && !state.calledIdentifiers.has(path.node)) { throw (0, error_1.default)(path, `the each() macro can only be used within a for ... of statement, like: for (let x of each(thing)){}`); } if (state.opts.owningPackageRoot) { // there is only an owningPackageRoot when we are running inside a // classic ember-cli build. In the embroider stage3 build, there is no // owning package root because we're compiling *all* packages // simultaneously. // // given that we're inside classic ember-cli, stop here without trying // to rewrite bare `require`. It's not needed, because both our // `importSync` and any user-written bare `require` can both mean the // same thing: runtime AMD `require`. return; } if (path.node.name === 'require' && !state.generatedRequires.has(path.node) && !path.scope.hasBinding('require') && state.owningPackage().isEmberAddon()) { // Our importSync macro has been compiled to `require`. But we want to // distinguish that from any pre-existing, user-written `require` in an // Ember addon, which should retain its *runtime* meaning. path.replaceWith(t.memberExpression(t.identifier('window'), path.node)); } }, }; if (context.types.OptionalMemberExpression) { // our getConfig and getOwnConfig macros are supposed to be able to absorb // optional chaining. To make that work we need to see the optional chaining // before preset-env compiles them away. visitor.OptionalMemberExpression = { enter(path, state) { if (state.opts.mode === 'compile-time') { let result = new evaluate_json_1.Evaluator({ state }).evaluate(path); if (result.confident) { path.replaceWith((0, evaluate_json_1.buildLiterals)(result.value, context)); } } }, }; } return { visitor }; } //# sourceMappingURL=macros-babel-plugin.js.map