diff --git a/packages/core/src/app.ts b/packages/core/src/app.ts index 810ac8a16..746dff0a1 100644 --- a/packages/core/src/app.ts +++ b/packages/core/src/app.ts @@ -35,7 +35,7 @@ import partition from 'lodash/partition'; import mergeWith from 'lodash/mergeWith'; import cloneDeep from 'lodash/cloneDeep'; import type { Params as InlineBabelParams } from './babel-plugin-inline-hbs-node'; -import { PortableHint } from './portable'; +import { PortableHint, maybeNodeModuleVersion } from './portable'; import escapeRegExp from 'escape-string-regexp'; import { getEmberExports } from './load-ember-template-compiler'; @@ -144,6 +144,38 @@ export function excludeDotFiles(files: string[]) { return files.filter(file => !file.startsWith('.') && !file.includes('/.')); } +export const CACHE_BUSTING_PLUGIN = { + path: require.resolve('./babel-plugin-cache-busting'), + version: readJSONSync(`${__dirname}/../package.json`).version, +}; + +export function addCachablePlugin(babelConfig: TransformOptions) { + if (Array.isArray(babelConfig.plugins) && babelConfig.plugins.length > 0) { + const plugins = Object.create(null); + plugins[CACHE_BUSTING_PLUGIN.path] = CACHE_BUSTING_PLUGIN.version; + + for (const plugin of babelConfig.plugins) { + let absolutePathToPlugin: string; + if (Array.isArray(plugin) && typeof plugin[0] === 'string') { + absolutePathToPlugin = plugin[0] as string; + } else if (typeof plugin === 'string') { + absolutePathToPlugin = plugin; + } else { + throw new Error(`[Embroider] a babel plugin without an absolute path was from: ${plugin}`); + } + + plugins[absolutePathToPlugin] = maybeNodeModuleVersion(absolutePathToPlugin); + } + + babelConfig.plugins.push([ + CACHE_BUSTING_PLUGIN.path, + { + plugins, + }, + ]); + } +} + class ParsedEmberAsset { kind: 'parsed-ember' = 'parsed-ember'; relativePath: string; @@ -383,7 +415,9 @@ export class AppBuilder { { absoluteRuntime: __dirname, useESModules: true, regenerator: false }, ]); - return makePortable(babel, { basedir: this.root }, this.portableHints); + const portable = makePortable(babel, { basedir: this.root }, this.portableHints); + addCachablePlugin(portable.config); + return portable; } private adjustImportsPlugin(engines: Engine[]): PluginItem { @@ -926,7 +960,7 @@ export class AppBuilder { return { requireFile: cursor, useMethod: hint.useMethod, - packageVersion: readJSONSync(cursor).version, + packageVersion: maybeNodeModuleVersion(cursor), }; }); } diff --git a/packages/core/src/babel-plugin-cache-busting.ts b/packages/core/src/babel-plugin-cache-busting.ts new file mode 100644 index 000000000..16d25884e --- /dev/null +++ b/packages/core/src/babel-plugin-cache-busting.ts @@ -0,0 +1,11 @@ +export default function makePlugin(): any { + // Dear future @rwjblue, + // + // This plugin exists as a sentinel plugin which has no behavior, but + // provides a position in the babel configuration to include cache busting + // meta-data about other plugins. Specifically their versions. + // + // Yours sincerely, + // Contributor + return {}; +} diff --git a/packages/core/src/portable.ts b/packages/core/src/portable.ts index d4bce561b..c2f6d9933 100644 --- a/packages/core/src/portable.ts +++ b/packages/core/src/portable.ts @@ -2,7 +2,6 @@ import mapValues from 'lodash/mapValues'; import assertNever from 'assert-never'; import { Memoize } from 'typescript-memoize'; import resolvePackagePath from 'resolve-package-path'; -import { readJSONSync } from 'fs-extra'; export const protocol = '__embroider_portable_values__'; const { globalValues, nonce } = setupGlobals(); @@ -25,10 +24,9 @@ export function maybeNodeModuleVersion(path: string) { const packagePath = findUpPackagePath(path); if (packagePath === null) { - // no package was found - return undefined; // should this bust the cache or ... ? + throw new Error(`Could not find package.json for '${path}'`); } else { - return readJSONSync(packagePath).version; + return require(packagePath).version; // eslint-disable-line @typescript-eslint/no-require-imports } } diff --git a/packages/core/tests/app.test.ts b/packages/core/tests/app.test.ts index cd698dd62..9b0704233 100644 --- a/packages/core/tests/app.test.ts +++ b/packages/core/tests/app.test.ts @@ -1,4 +1,4 @@ -import { excludeDotFiles } from '../src/app'; +import { excludeDotFiles, addCachablePlugin, CACHE_BUSTING_PLUGIN } from '../src/app'; describe('dot files can be excluded', () => { test('excludeDotFiles works', () => { @@ -10,3 +10,46 @@ describe('dot files can be excluded', () => { expect(excludeDotFiles(['foo/bar/baz/.foo.js'])).toEqual([]); }); }); + +describe('cacheable-plugin', function () { + test('noop', function () { + const input = {}; + addCachablePlugin(input); + expect(input).toEqual({}); + }); + + test('no plugins', function () { + const input = { plugins: [] }; + addCachablePlugin(input); + expect(input).toEqual({ plugins: [] }); + }); + + test('some plugins', function () { + const input = { + plugins: [__dirname, [__dirname, []], [`${__dirname}/../`, []], __dirname, [__dirname, []]], + }; + + addCachablePlugin(input); + + expect(input).toEqual({ + plugins: [ + __dirname, + [__dirname, []], + [`${__dirname}/../`, []], + __dirname, + [__dirname, []], + + [ + CACHE_BUSTING_PLUGIN.path, + { + plugins: { + [CACHE_BUSTING_PLUGIN.path]: CACHE_BUSTING_PLUGIN.version, + [__dirname]: CACHE_BUSTING_PLUGIN.version, + [`${__dirname}/../`]: CACHE_BUSTING_PLUGIN.version, + }, + }, + ], + ], + }); + }); +}); diff --git a/packages/core/tests/portable.test.ts b/packages/core/tests/portable.test.ts index 813dc555c..355e08700 100644 --- a/packages/core/tests/portable.test.ts +++ b/packages/core/tests/portable.test.ts @@ -1,14 +1,15 @@ import { maybeNodeModuleVersion } from '../src/portable'; import { readJSONSync } from 'fs-extra'; -const EMBROIDER_CORE_VERSION = readJSONSync('../../package.json').version; +const EMBROIDER_CORE_VERSION = readJSONSync(`${__dirname}/../package.json`).version; describe('maybeNodeModuleVersion', () => { test('it', () => { - expect(maybeNodeModuleVersion('/dev/null')).toEqual(undefined); - expect(maybeNodeModuleVersion('/does/not/exist')).toEqual(undefined); + expect(() => maybeNodeModuleVersion('/dev/null')).toThrow(/Could not find package.json for '\/dev\/null'/); + expect(() => maybeNodeModuleVersion('/does/not/exist')).toThrow( + /Could not find package.json for '\/does\/not\/exist'/ + ); expect(maybeNodeModuleVersion(__dirname)).toEqual(EMBROIDER_CORE_VERSION); expect(maybeNodeModuleVersion(__filename)).toEqual(EMBROIDER_CORE_VERSION); }); }); - diff --git a/test-packages/macro-sample-addon/package.json b/test-packages/macro-sample-addon/package.json index d2c158049..aee6b8cf2 100644 --- a/test-packages/macro-sample-addon/package.json +++ b/test-packages/macro-sample-addon/package.json @@ -36,6 +36,7 @@ "@embroider/test-support": "0.36.0", "@embroider/webpack": "0.41.0", "broccoli-asset-rev": "^3.0.0", + "ember-cli-dependency-checker": "^3.1.0", "ember-cli": "~3.26.1", "ember-cli-eslint": "^5.1.0", "ember-cli-inject-live-reload": "^1.8.2", @@ -48,7 +49,7 @@ "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.4.1", "ember-resolver": "^5.0.1", - "ember-source": "~3.16.0", + "ember-source": "~3.10.0", "ember-source-channel-url": "^1.1.0", "ember-try": "^1.0.0", "ember-welcome-page": "^4.0.0", @@ -66,4 +67,4 @@ "volta": { "extends": "../../package.json" } -} \ No newline at end of file +}