UNPKG

ember-auto-import

Version:
580 lines 25.1 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.reloadDevPackages = reloadDevPackages; const resolve_package_path_1 = __importDefault(require("resolve-package-path")); const path_1 = require("path"); const fs_1 = require("fs"); const typescript_memoize_1 = require("typescript-memoize"); const shared_internals_1 = require("@embroider/shared-internals"); const semver_1 = __importDefault(require("semver")); const node_1 = require("@embroider/macros/src/node"); const minimatch_1 = __importDefault(require("minimatch")); const util_1 = require("./util"); const watch_utils_1 = require("./watch-utils"); // from child addon instance to their parent package const parentCache = new WeakMap(); // from an addon instance or project to its package const packageCache = new WeakMap(); let pkgGeneration = 0; function reloadDevPackages() { pkgGeneration++; } class Package { static lookupParentOf(child, extraResolve) { if (!parentCache.has(child)) { let pkg = packageCache.get(child.parent); if (!pkg) { pkg = new this(child, extraResolve); packageCache.set(child.parent, pkg); } parentCache.set(child, pkg); } return parentCache.get(child); } constructor(child, extraResolve) { this._hasBabelDetails = false; this.name = child.parent.pkg.name; this.extraResolve = extraResolve; if ((0, shared_internals_1.isDeepAddonInstance)(child)) { this.root = this.pkgRoot = child.parent.root; this.isAddon = true; this.isDeveloping = this.root === child.project.root; // This is the per-package options from ember-cli this._options = child.parent.options; } else { // this can differ from child.parent.root because Dummy apps are terrible this.root = (0, path_1.join)(child.project.configPath(), '..', '..'); this.pkgRoot = child.parent.root; this.isAddon = false; this.isDeveloping = true; this._options = child.app.options; this.macrosConfig = node_1.MacrosConfig.for(child.app, this.root); } this._parent = child.parent; // Stash our own config options this.autoImportOptions = this._options.autoImport; this.pkgCache = child.parent.pkg; this.pkgGeneration = pkgGeneration; } _ensureBabelDetails() { if (this._hasBabelDetails) { return; } let { babelOptions, extensions, version } = this.buildBabelOptions(this._parent, this._options); this._emberCLIBabelExtensions = extensions; this._babelOptions = babelOptions; this._babelMajorVersion = version; this._hasBabelDetails = true; } // this is used for two things: // - when interoperating with older versions of ember-auto-import, it's used // to configure the parser that we use to analyze source code. The parser // cares about the user's babel config so it will support all the same // syntax. (Newer EAI versions don't need to do this because they use the // faster analyzer that happens inside the existing babel parse.) // - when transpiling parts of the app itself that are configured with // allowAppImports. It would be surprising if these didn't get transpiled // with the same babel config that the rest of the app is getting. There is, // however, one exception: if the user has added // ember-auto-import/babel-plugin to get dynamic import support, we need to // remove that because inside the natively webpack-owned area it's not // needed and would actually break dynamic imports. get babelOptions() { this._ensureBabelDetails(); return this._babelOptions; } get babelMajorVersion() { this._ensureBabelDetails(); return this._babelMajorVersion; } get isFastBootEnabled() { return (process.env.FASTBOOT_DISABLED !== 'true' && this._parent.addons.some((addon) => addon.name === 'ember-cli-fastboot')); } // each package implicitly imports the `implicit-modules` declared by its v2 // addon dependencies, just like in Embroider. get implicitImports() { return [ ...this.extraResolve .implicitImports('implicit-modules', this.pkgRoot) .map((specifier) => ({ isDynamic: false, specifier, path: './-eai-implicit-modules.js', package: this, treeType: 'app', })), ...this.extraResolve .implicitImports('implicit-test-modules', this.pkgRoot) .map((specifier) => ({ isDynamic: false, specifier, path: './-eai-implicit-modules.js', package: this, treeType: 'test', })), ]; } buildBabelOptions(instance, options) { // Generate the same babel options that the package (meaning app or addon) // is using. We will use these so we can configure our parser to // match. let babelAddon = instance.addons.find((addon) => addon.name === 'ember-cli-babel'); let version = parseInt(babelAddon.pkg.version.split('.')[0], 10); let babelOptions, extensions; babelOptions = babelAddon.buildBabelOptions(Object.assign(Object.assign({}, options), { 'ember-cli-babel': Object.assign(Object.assign({}, options['ember-cli-babel']), { compileModules: false, disableEmberModulesAPIPolyfill: false }) })); extensions = babelOptions.filterExtensions || ['js']; // https://github.com/babel/ember-cli-babel/issues/227 delete babelOptions.annotation; delete babelOptions.throwUnlessParallelizable; delete babelOptions.filterExtensions; if (babelOptions.plugins) { babelOptions.plugins = babelOptions.plugins.filter( // removing the weird "_parallelBabel" entry that's only used by // broccoli-babel-transpiler and removing our own dynamic import babel // plugin if it was added (because it's only correct to use it against // the classic ember build, not the webpack-owned parts of the build. (p) => !p._parallelBabel && !isEAIBabelPlugin(p)); } return { babelOptions, extensions, version }; } get pkg() { if (!this.pkgCache || (this.isDeveloping && pkgGeneration !== this.pkgGeneration)) { // avoiding `require` here because we don't want to go through the // require cache. this.pkgCache = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(this.pkgRoot, 'package.json'), 'utf-8')); this.pkgGeneration = pkgGeneration; } return this.pkgCache; } get namespace() { // This namespacing ensures we can be used by multiple packages as // well as by an addon and its dummy app simultaneously return `${this.name}/${this.isAddon ? 'addon' : 'app'}`; } hasDependency(name) { var _a, _b, _c; let { pkg } = this; return Boolean(((_a = pkg.dependencies) === null || _a === void 0 ? void 0 : _a[name]) || ((_b = pkg.devDependencies) === null || _b === void 0 ? void 0 : _b[name]) || ((_c = pkg.peerDependencies) === null || _c === void 0 ? void 0 : _c[name]) || this.extraResolve.hasV2Addon(name)); } // the semver range of the given package that our package requests in // package.json requestedRange(packageName) { var _a, _b, _c; let { pkg } = this; let result = ((_a = pkg.dependencies) === null || _a === void 0 ? void 0 : _a[packageName]) || ((_b = pkg.peerDependencies) === null || _b === void 0 ? void 0 : _b[packageName]); // only include devDeps if the package is an app if (!result && !this.isAddon) { result = (_c = pkg.devDependencies) === null || _c === void 0 ? void 0 : _c[packageName]; } return result; } hasNonDevDependency(name) { var _a, _b; let pkg = this.pkg; return Boolean(((_a = pkg.dependencies) === null || _a === void 0 ? void 0 : _a[name]) || ((_b = pkg.peerDependencies) === null || _b === void 0 ? void 0 : _b[name]) || this.extraResolve.hasV2Addon(name)); } static categorize(importedPath, partial = false) { if (/^(\w+:)?\/\//.test(importedPath) || importedPath.startsWith('data:')) { return 'url'; } if (importedPath[0] === '.' || importedPath[0] === '/') { return 'local'; } if (partial && !isPrecise(importedPath)) { return 'imprecise'; } return 'dep'; } resolve(importedPath, fromPath, partial = false) { switch (Package.categorize(importedPath, partial)) { case 'url': return { type: 'url', url: importedPath }; case 'local': return { type: 'local', local: importedPath, }; case 'imprecise': if (partial) { return { type: 'imprecise', }; } break; } let path = this.extraResolve.handleRenaming(this.aliasFor(importedPath)); let packageName = (0, shared_internals_1.packageName)(path); if (!packageName) { // this can only happen if the user supplied an alias that points at a // relative or absolute path, rather than a package name. If the // originally authored import was an absolute or relative path, it would // have hit our { type: 'local' } condition before we ran aliasFor. // // At the moment, we don't try to handle this case, but we could in the // future. return { type: 'local', local: path, }; } if (!this.isAddon && packageName === this.name) { let localPath = path.slice(packageName.length + 1); if (this.allowAppImports.some((pattern) => (0, minimatch_1.default)((0, util_1.stripQuery)(localPath), pattern))) { return { type: 'package', packageName: this.name, packageRoot: (0, path_1.join)(this.root, 'app'), resolvedSpecifier: path, }; } } if (this.excludesDependency(packageName)) { // This package has been explicitly excluded. return; } if (!this.hasDependency(packageName)) { return; } let packageRoot; let packagePath = (0, resolve_package_path_1.default)(packageName, this.root); if (packagePath) { packageRoot = (0, path_1.dirname)(packagePath); } if (!packageRoot) { packageRoot = this.extraResolve.v2AddonRoot(packageName); } if (packageRoot == null) { throw new Error(`${this.name} tried to import "${packageName}" in "${fromPath}" but the package was not resolvable from ${this.root}`); } if (isV1EmberAddonDependency(packageRoot)) { // ember addon are not auto imported return; } this.assertAllowedDependency(packageName, fromPath); return { type: 'package', packageName, packageRoot, resolvedSpecifier: path, }; } assertAllowedDependency(name, fromPath) { if (this.isAddon && !this.hasNonDevDependency(name)) { throw new Error(`${this.name} tried to import "${name}" in "${fromPath}" from addon code, but "${name}" is a devDependency. You may need to move it into dependencies.`); } } excludesDependency(name) { return Boolean(this.autoImportOptions && this.autoImportOptions.exclude && this.autoImportOptions.exclude.includes(name)); } get webpackConfig() { return this.autoImportOptions && this.autoImportOptions.webpack; } get skipBabel() { return this.autoImportOptions && this.autoImportOptions.skipBabel; } get aliases() { var _a; return (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.alias; } // this follows the same rules as webpack's resolve.alias. It's a prefix // match, unless the configured pattern ends with "$" in which case that means // exact match. aliasFor(name) { var _a; let alias = (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.alias; if (!alias) { return name; } let exactMatch = alias[`${name}$`]; if (exactMatch) { return exactMatch; } let prefixMatch = Object.keys(alias).find((pattern) => name.startsWith(pattern)); if (prefixMatch && alias[prefixMatch]) { return alias[prefixMatch] + name.slice(prefixMatch.length); } return name; } get fileExtensions() { this._ensureBabelDetails(); // type safety: this will have been populated by the call above return this._emberCLIBabelExtensions; } publicAssetURL() { var _a, _b; if (this.isAddon) { throw new Error(`bug: only the app should control publicAssetURL`); } return ensureTrailingSlash((_b = (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.publicAssetURL) !== null && _b !== void 0 ? _b : ensureTrailingSlash(this._parent.config().rootURL) + 'assets/'); } get styleLoaderOptions() { var _a; // only apps (not addons) are allowed to set this return this.isAddon ? undefined : (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.styleLoaderOptions; } get cssLoaderOptions() { var _a; // only apps (not addons) are allowed to set this return this.isAddon ? undefined : (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.cssLoaderOptions; } get miniCssExtractPluginOptions() { var _a; // only apps (not addons) are allowed to set this return this.isAddon ? undefined : (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.miniCssExtractPluginOptions; } get forbidsEval() { // only apps (not addons) are allowed to set this, because it's motivated by // the apps own Content Security Policy. return Boolean(!this.isAddon && this.autoImportOptions && this.autoImportOptions.forbidEval); } get insertScriptsAt() { var _a; if (this.isAddon) { throw new Error(`bug: only apps should control insertScriptsAt`); } return (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.insertScriptsAt; } get insertStylesAt() { var _a; if (this.isAddon) { throw new Error(`bug: only apps should control insertStylesAt`); } return (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.insertStylesAt; } get watchedDirectories() { var _a; // only apps (not addons) are allowed to set this if (!this.isAddon && ((_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.watchDependencies)) { return this.autoImportOptions.watchDependencies .map((nameOrNames) => { let names; if (typeof nameOrNames === 'string') { names = [nameOrNames]; } else { names = nameOrNames; } let cursor = this.root; for (let name of names) { let path = (0, resolve_package_path_1.default)(name, cursor); if (!path) { return []; } cursor = (0, path_1.dirname)(path); } return (0, watch_utils_1.getWatchedDirectories)(cursor).map((relativeDir) => (0, path_1.join)(cursor, relativeDir)); }) .flat(); } } get allowAppImports() { var _a, _b; // only apps (not addons) are allowed to set this if (!this.isAddon) { return (_b = (_a = this.autoImportOptions) === null || _a === void 0 ? void 0 : _a.allowAppImports) !== null && _b !== void 0 ? _b : []; } return []; } cleanBabelConfig() { if (this.isAddon) { throw new Error(`Only the app can generate auto-import's babel config`); } // casts here are safe because we just checked isAddon is false let parent = this._parent; let macrosConfig = this.macrosConfig; let emberSource = parent.addons.find((addon) => addon.name === 'ember-source'); if (!emberSource) { throw new Error(`failed to find ember-source in addons of ${this.name}`); } let ensureModuleApiPolyfill = semver_1.default.satisfies(emberSource.pkg.version, '<3.27.0', { includePrerelease: true }); let templateCompilerPath; if (emberSource.absolutePaths) { templateCompilerPath = emberSource.absolutePaths .templateCompiler; } else { // v7+ ember-source no longer provides absolutePaths; resolve from // the host app's directory through the package exports map // eslint-disable-next-line @typescript-eslint/no-var-requires let { createRequire } = require('module'); let appRequire = createRequire((0, path_1.join)(this.root, 'package.json')); templateCompilerPath = appRequire.resolve('ember-source/ember-template-compiler/index.js'); } const babelPluginPrecompile = ensureModuleApiPolyfill ? [ require.resolve('babel-plugin-htmlbars-inline-precompile'), { ensureModuleApiPolyfill, templateCompilerPath, modules: { 'ember-cli-htmlbars': 'hbs', '@ember/template-compilation': { export: 'precompileTemplate', disableTemplateLiteral: true, shouldParseScope: true, isProduction: process.env.EMBER_ENV === 'production', }, }, }, ] : [ require.resolve('babel-plugin-ember-template-compilation'), { // As above, we present the AST transforms in reverse order // transforms: [...pluginInfo.plugins].reverse(), compilerPath: require.resolve(templateCompilerPath), enableLegacyModules: [ 'ember-cli-htmlbars', 'ember-cli-htmlbars-inline-precompile', 'htmlbars-inline-precompile', ], }, 'ember-cli-htmlbars:inline-precompile', ]; let plugins = [ [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }], [require.resolve('@babel/plugin-transform-class-static-block')], [ require.resolve('@babel/plugin-proposal-class-properties'), { loose: false }, ], [ require.resolve('@babel/plugin-proposal-private-methods'), { loose: false }, ], babelPluginPrecompile, ...macrosConfig.babelPluginConfig(), ]; if (ensureModuleApiPolyfill) { plugins.push([ require.resolve('babel-plugin-ember-modules-api-polyfill'), ]); } // this is to facilitate testing external dependencies against our cleanBabelConfig. // We only want to do this in our own testing as it checks for the name of all string // identifiers and is only ever going to be necessary in our tests. // previously we tested that a `let` got transpiled to a var, but since the IE11 target // was removed that test wasn't checking the right thing. This was the simplest way that // we could think to test that would be future-proof if (process.env.USE_EAI_BABEL_WATERMARK) { plugins.push([require.resolve('./watermark-plugin')]); } return { // do not use the host project's own `babel.config.js` file. Only a strict // subset of features are allowed in the third-party code we're // transpiling. // // - every package gets babel preset-env unless skipBabel is configured // for them. // - because we process v2 ember packages, we enable inline hbs (with no // custom transforms) and modules-api-polyfill configFile: false, babelrc: false, // leaving this unset can generate an unhelpful warning from babel on // large files like 'Note: The code generator has deoptimised the // styling of... as it exceeds the max of 500KB." generatorOpts: { compact: true, }, plugins, presets: [ [ require.resolve('@babel/preset-env'), { modules: false, targets: parent.targets, }, ], ], }; } browserslist() { if (this.isAddon) { throw new Error(`Only the app can determine the browserslist`); } let parent = this._parent; return parent.targets.browsers.join(','); } } exports.default = Package; __decorate([ (0, typescript_memoize_1.Memoize)() ], Package.prototype, "isFastBootEnabled", null); __decorate([ (0, typescript_memoize_1.Memoize)() ], Package.prototype, "implicitImports", null); const isAddonCache = new Map(); function isV1EmberAddonDependency(packageRoot) { var _a, _b, _c; let cached = isAddonCache.get(packageRoot); if (cached === undefined) { // eslint-disable-next-line @typescript-eslint/no-var-requires let packageJSON = require((0, path_1.join)(packageRoot, 'package.json')); let answer = ((_a = packageJSON.keywords) === null || _a === void 0 ? void 0 : _a.includes('ember-addon')) && ((_c = (_b = packageJSON['ember-addon']) === null || _b === void 0 ? void 0 : _b.version) !== null && _c !== void 0 ? _c : 1) < 2; isAddonCache.set(packageRoot, answer); return answer; } else { return cached; } } function count(str, letter) { return [...str].reduce((a, b) => a + (b === letter ? 1 : 0), 0); } function isPrecise(leadingQuasi) { if (leadingQuasi.startsWith('.') || leadingQuasi.startsWith('/')) { return true; } let slashes = count(leadingQuasi, '/'); let minSlashes = leadingQuasi.startsWith('@') ? 2 : 1; return slashes >= minSlashes; } function ensureTrailingSlash(url) { if (url[url.length - 1] !== '/') { url = url + '/'; } return url; } function isEAIBabelPlugin(item) { var _a, _b; let pluginPath; if (typeof item === 'string') { pluginPath = item; } else if (Array.isArray(item) && item.length > 0 && typeof item[0] === 'string') { pluginPath = item[0]; } if (pluginPath) { return /ember-auto-import[\\/]babel-plugin/.test(pluginPath); } return ((_b = (_a = item).baseDir) === null || _b === void 0 ? void 0 : _b.call(_a)) === __dirname; } //# sourceMappingURL=package.js.map