diff --git a/packages/compat/package.json b/packages/compat/package.json index d44fd3bc8..98c25ae34 100644 --- a/packages/compat/package.json +++ b/packages/compat/package.json @@ -64,6 +64,7 @@ "devDependencies": { "@embroider/sample-transforms": "0.0.0", "@embroider/test-support": "0.36.0", + "@glimmer/syntax": "0.80.0", "@types/babel__core": "^7.1.14", "@types/babel__generator": "^7.6.2", "@types/babel__template": "^7.4.0", @@ -77,9 +78,9 @@ "@types/resolve": "^1.20.0", "@types/semver": "^7.3.6", "@types/strip-bom": "^4.0.1", - "ember-cli-htmlbars-inline-precompile": "^2.1.0", + "broccoli-stew": "^3.0.0", "ember-cli-htmlbars-3": "npm:ember-cli-htmlbars@3", - "@glimmer/syntax": "0.80.0", + "ember-cli-htmlbars-inline-precompile": "^2.1.0", "ember-engines": "^0.8.19", "typescript": "*" }, diff --git a/packages/compat/src/v1-addon.ts b/packages/compat/src/v1-addon.ts index c7b69ef9d..eefdef51a 100644 --- a/packages/compat/src/v1-addon.ts +++ b/packages/compat/src/v1-addon.ts @@ -488,14 +488,26 @@ export default class V1Addon { // applies preprocessors to JS and HBS private transpile(tree: Node) { + // Namespace the tree being passed to preprocessJs with the moduleName + // to mimic classic build + tree = buildFunnel(tree, { destDir: this.moduleName }); + tree = this.addonInstance.preprocessJs(tree, '/', this.moduleName, { registry: this.addonInstance.registry, }); + + // Remove namespacing so that it gets written out to the node_modules + // directory correctly. + tree = buildFunnel(tree, { + srcDir: this.moduleName, + }); + if (this.addonInstance.shouldCompileTemplates() && this.addonInstance.registry.load('template')?.length > 0) { tree = this.app.preprocessRegistry.preprocessTemplates(tree, { registry: this.addonInstance.registry, }); } + return tree; } diff --git a/packages/compat/src/v1-app.ts b/packages/compat/src/v1-app.ts index 1d12d2396..ac06e7fbf 100644 --- a/packages/compat/src/v1-app.ts +++ b/packages/compat/src/v1-app.ts @@ -551,10 +551,16 @@ export default class V1App { // auto-import is supported natively so we don't need it here this.app.registry.remove('js', 'ember-auto-import-analyzer'); - return this.preprocessors.preprocessJs(tree, `/`, '/', { + tree = buildFunnel(tree, { destDir: this.name }); + + tree = this.preprocessors.preprocessJs(tree, `/`, '/', { annotation: 'v1-app-preprocess-js', registry: this.app.registry, }); + + tree = buildFunnel(tree, { srcDir: this.name }); + + return tree; } get htmlbarsPlugins(): TemplateCompilerPlugins { diff --git a/packages/compat/tests/preprocessors.test.ts b/packages/compat/tests/preprocessors.test.ts new file mode 100644 index 000000000..4ee7c840f --- /dev/null +++ b/packages/compat/tests/preprocessors.test.ts @@ -0,0 +1,126 @@ +import { Project, BuildResult, expectFilesAt, ExpectFile } from '@embroider/test-support'; +import { throwOnWarnings } from '@embroider/core'; +import merge from 'lodash/merge'; + +describe('preprocessors tests', function () { + jest.setTimeout(120000); + let build: BuildResult; + let app: Project; + let expectFile: ExpectFile; + + throwOnWarnings(); + + beforeAll(async function () { + app = Project.emberNew('my-app'); + + merge(app.files, { + config: { + 'targets.js': `module.exports = { browsers: ['last 1 Chrome versions'] }`, + }, + app: { + components: { + 'from-the-app.js': ` + import Component from '@glimmer/component'; + export default class extends Component {} + `, + 'from-the-app.hbs': `
{{this.title}}
`, + }, + }, + }); + + const PACKAGE_MY_PREPROCESSOR = 'my-preprocessor'; + let addonPreprocessor = app.addAddon(PACKAGE_MY_PREPROCESSOR); + + const INDEX_JS_WITH_PREPROCESSOR = `const { map } = require('broccoli-stew'); + +module.exports = { + name: require('./package').name, + + setupPreprocessorRegistry(type, registry) { + if (type !== 'parent') { + return; + } + + registry.add('js', { + name: 'special-path-processor', + toTree(tree, inputPath) { + if (inputPath !== '/') { + return tree; + } + + let augmented = map( + tree, + '**/*.{js,css}', + function (content, relativePath) { + return \`/*path@\${relativePath}*/\n\${content}\`; + } + ); + return augmented; + }, + }); + } +}; +`; + + addonPreprocessor.linkDevPackage('broccoli-stew'); + addonPreprocessor.files['index.js'] = INDEX_JS_WITH_PREPROCESSOR; + + let addon = app.addAddon('my-addon'); + + merge(addon.files, { + app: { + components: { + 'greeting.js': `export { default } from 'my-addon/components/greeting';`, + }, + }, + addon: { + components: { + 'greeting.js': ` + import Component from '@glimmer/component'; + export default class extends Component {} + `, + 'greeting.hbs': `Hello World`, + }, + }, + }); + + // We must explicitly pass the addonPreprocessor using the + // name is not sufficient. + addon.addDependency(addonPreprocessor); + + build = await BuildResult.build(app, { + stage: 2, + type: 'app', + emberAppOptions: { + tests: false, + }, + }); + + expectFile = expectFilesAt(build.outputPath); + }); + + afterAll(async () => { + await build.cleanup(); + }); + + test('dependencies are setup for this test suite correctly', () => { + expectFile('package.json').exists(); + expectFile('package.json').matches(/my-preprocessor/, 'has the preprocessor dependency'); + expectFile('node_modules/my-addon/package.json').exists(); + expectFile('node_modules/my-addon/package.json').matches(/my-preprocessor/, 'has the preprocessor dependency'); + expectFile('node_modules/my-preprocessor/package.json').exists(); + }); + + test('app has correct path embedded in comment', () => { + const assertFile = expectFile('components/from-the-app.js'); + assertFile.exists(); + // This is the expected output during an classic build. + assertFile.matches(/path@my-app\/components\/from-the-app\.js/, 'has a path comment in app components'); + }); + + test('addon has correct path embedded in comment', () => { + expectFile('node_modules/my-preprocessor/package.json').exists(); + const assertFile = expectFile('node_modules/my-addon/components/greeting.js'); + assertFile.matches(/path@my-addon\/components\/greeting\.js/, 'has a path comment in app components'); + }); +});