Skip to content

Commit

Permalink
use app babel config on allowAppImport files
Browse files Browse the repository at this point in the history
  • Loading branch information
mansona committed Nov 2, 2023
1 parent f3bc777 commit bfe310a
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 133 deletions.
5 changes: 0 additions & 5 deletions packages/ember-auto-import/ts/auto-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,9 @@ export default class AutoImport implements AutoImportSharedAPI {
splitter,
environment: this.env,
packages: this.packages,
appRoot: this.rootPackage.root,
consoleWrite: this.consoleWrite,
bundles: this.bundles,
babelConfig: this.rootPackage.cleanBabelConfig(),
browserslist: this.rootPackage.browserslist(),
publicAssetURL: this.rootPackage.publicAssetURL(),
webpack,
hasFastboot: this.rootPackage.isFastBootEnabled,
v2Addons: this.v2Addons,
rootPackage: this.rootPackage,
});
Expand Down
6 changes: 0 additions & 6 deletions packages/ember-auto-import/ts/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type Package from './package';
import type BundleConfig from './bundle-config';
import type { BundleName } from './bundle-config';
import { buildDebugCallback } from 'broccoli-debug';
import type { TransformOptions } from '@babel/core';
import type webpack from 'webpack';

const debugTree = buildDebugCallback('ember-auto-import');
Expand All @@ -14,13 +13,8 @@ export interface BundlerOptions {
environment: 'development' | 'test' | 'production';
splitter: Splitter;
packages: Set<Package>;
appRoot: string;
bundles: BundleConfig;
babelConfig: TransformOptions;
publicAssetURL: string | undefined;
browserslist: string;
webpack: typeof webpack;
hasFastboot: boolean;
v2Addons: Map<string, string>;
rootPackage: Package;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/ember-auto-import/ts/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,16 @@ export default class Package {
]);
}

// 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
Expand Down Expand Up @@ -612,7 +622,7 @@ export default class Package {
if (this.isAddon) {
throw new Error(`Only the app can determine the browserslist`);
}
// cast here is safe because we just checked isAddon is false

let parent = this._parent as Project;
return (parent.targets as { browsers: string[] }).browsers.join(',');
}
Expand Down
17 changes: 17 additions & 0 deletions packages/ember-auto-import/ts/watermark-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { NodePath } from '@babel/traverse';
import type * as t from '@babel/types';
import type * as Babel from '@babel/core';

export default function watermark(babel: { types: typeof t }): Babel.PluginObj {
return {
visitor: {
Identifier(path: NodePath<t.Identifier>) {
if (path.node.name === '__EAI_WATERMARK__') {
path.replaceWith(
babel.types.stringLiteral('successfully watermarked')
);
}
},
},
};
}
59 changes: 47 additions & 12 deletions packages/ember-auto-import/ts/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import makeDebug from 'debug';
import { ensureDirSync, symlinkSync, existsSync } from 'fs-extra';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import minimatch from 'minimatch';
import { TransformOptions } from '@babel/core';

const EXTENSIONS = ['.js', '.ts', '.json'];

Expand Down Expand Up @@ -157,10 +158,10 @@ export default class WebpackBundler extends Plugin implements Bundler {
// this controls webpack's own runtime code generation. You still need
// preset-env to preprocess the libraries themselves (which is already
// part of this.opts.babelConfig)
target: `browserslist:${this.opts.browserslist}`,
target: `browserslist:${this.opts.rootPackage.browserslist()}`,
output: {
path: join(this.outputPath, 'assets'),
publicPath: this.opts.publicAssetURL,
publicPath: this.opts.rootPackage.publicAssetURL(),
filename: `chunk.[id].[chunkhash].js`,
chunkFilename: `chunk.[id].[chunkhash].js`,
libraryTarget: 'var',
Expand Down Expand Up @@ -198,7 +199,16 @@ export default class WebpackBundler extends Plugin implements Bundler {
module: {
noParse: (file: string) => file === join(stagingDir, 'l.cjs'),
rules: [
this.babelRule(stagingDir),
this.babelRule(
stagingDir,
(filename) => !this.fileIsInApp(filename),
this.opts.rootPackage.cleanBabelConfig()
),
this.babelRule(
stagingDir,
(filename) => this.fileIsInApp(filename),
this.opts.rootPackage.babelOptions
),
{
test: /\.css$/i,
use: [
Expand Down Expand Up @@ -232,7 +242,10 @@ export default class WebpackBundler extends Plugin implements Bundler {
loader: RuleSetUseItem;
plugin: WebpackPluginInstance | undefined;
} {
if (this.opts.environment === 'production' || this.opts.hasFastboot) {
if (
this.opts.environment === 'production' ||
this.opts.rootPackage.isFastBootEnabled
) {
return {
loader: MiniCssExtractPlugin.loader,
plugin: new MiniCssExtractPlugin({
Expand Down Expand Up @@ -265,22 +278,44 @@ export default class WebpackBundler extends Plugin implements Bundler {
return output;
}

private babelRule(stagingDir: string): RuleSetRule {
let shouldTranspile = babelFilter(this.skipBabel(), this.opts.appRoot);
private fileIsInApp(filename: string) {
let packageCache = PackageCache.shared(
'ember-auto-import',
this.opts.rootPackage.root
);

const pkg = packageCache.ownerOfFile(filename);

return pkg?.root === this.opts.rootPackage.root;
}

private babelRule(
stagingDir: string,
filter: (filename: string) => boolean,
babelConfig: TransformOptions
): RuleSetRule {
let shouldTranspile = babelFilter(
this.skipBabel(),
this.opts.rootPackage.root
);

return {
test(filename: string) {
test: (filename: string) => {
// We don't apply babel to our own stagingDir (it contains only our own
// entrypoints that we wrote, and it can use `import()`, which we want
// to leave directly for webpack).
//
// And we otherwise defer to the `skipBabel` setting as implemented by
// `@embroider/shared-internals`.
return dirname(filename) !== stagingDir && shouldTranspile(filename);
return (
dirname(filename) !== stagingDir &&
shouldTranspile(filename) &&
filter(filename)
);
},
use: {
loader: 'babel-loader-8',
options: this.opts.babelConfig,
options: babelConfig,
},
};
}
Expand All @@ -291,7 +326,7 @@ export default class WebpackBundler extends Plugin implements Bundler {
private get externalsHandler(): Configuration['externals'] {
let packageCache = PackageCache.shared(
'ember-auto-import',
this.opts.appRoot
this.opts.rootPackage.root
);
return (params, callback) => {
let { context, request, contextInfo } = params;
Expand Down Expand Up @@ -407,7 +442,7 @@ export default class WebpackBundler extends Plugin implements Bundler {
return false;
}

if (pkg.root !== this.opts.appRoot) {
if (pkg.root !== this.opts.rootPackage.root) {
return false;
}

Expand Down Expand Up @@ -558,7 +593,7 @@ export default class WebpackBundler extends Plugin implements Bundler {
deps.dynamicTemplateImports.map(mapTemplateImports),
staticTemplateImports:
deps.staticTemplateImports.map(mapTemplateImports),
publicAssetURL: this.opts.publicAssetURL,
publicAssetURL: this.opts.rootPackage.publicAssetURL(),
})
);
}
Expand Down
177 changes: 177 additions & 0 deletions test-scenarios/babel-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import merge from 'lodash/merge';
import { appScenarios } from './scenarios';
import { PreparedApp, Project } from 'scenario-tester';
import QUnit from 'qunit';
const { module: Qmodule, test } = QUnit;

appScenarios
.map('babel', project => {
let aModuleDependency = new Project({
files: {
'package.json': '{ "name": "a-module-dependency", "version": "0.0.1" }',
'index.js': `
export default function aModuleDependency() {
if (typeof __EAI_WATERMARK__ === "string" && __EAI_WATERMARK__ === "successfully watermarked") {
return 'module transpiled with cleanBabelConfig';
} else {
return 'module not transpiled with cleanBabelConfig';
}
}`,
},
});

let bModuleDependency = new Project({
files: {
'package.json': '{ "name": "b-module-dependency", "version": "0.1.1" }',
'index.js': `
export function externalLibUsingTranspileTargetFunction() {
try {
return TRANSPILE_TARGET();
} catch (e) {
return "this function has not been transpiled with our custom babel config";
}
}
export function needsToBeTranspiled() {
if (typeof __EAI_WATERMARK__ === "string" && __EAI_WATERMARK__ === "successfully watermarked") {
return 'module transpiled with cleanBabelConfig';
} else {
return 'module not transpiled with cleanBabelConfig';
}
}`,
},
});
project.addDevDependency(aModuleDependency);
project.addDevDependency(bModuleDependency);
project.linkDevDependency('ember-auto-import', { baseDir: __dirname });
project.linkDependency('webpack', { baseDir: __dirname });

merge(project.files, {
'ember-cli-build.js': EMBER_CLI_BUILD_JS,
app: {
lib: {
'example1.js': `export default function() {
return TRANSPILE_TARGET();
}
export function ensureNotWatermarked() {
return typeof __EAI_WATERMARK__ === "undefined"
}
`,
},
controllers: {
'application.js': APPLICATION_JS,
},
templates: {
'application.hbs': '<div data-test-import-result>{{this.moduleResult}}</div>',
},
},
tests: {
acceptance: {
'basic-test.js': BASIC_TEST_JS,
},
unit: {
'babel-transform-app-code-test.js': `
import { module, test } from 'qunit';
import example1, { ensureNotWatermarked } from '@ef4/app-template/lib/example1';
import { needsToBeTranspiled, externalLibUsingTranspileTargetFunction} from 'b-module-dependency';
module('Unit | babel-transform-app-code tests', function () {
test('it successfully transforms code imported using allowAppImports', function (assert) {
assert.equal(example1(), 'it woked');
assert.ok(ensureNotWatermarked(), 'app code doesnt get 3rd party babel config');
});
test('it transpiles external deps but doesnt use the apps config', function(assert) {
assert.equal(needsToBeTranspiled(), 'module transpiled with cleanBabelConfig');
assert.equal(externalLibUsingTranspileTargetFunction(), 'this function has not been transpiled with our custom babel config');
})
});
`,
},
},
});
})
.forEachScenario(scenario => {
Qmodule(scenario.name, function (hooks) {
let app: PreparedApp;
hooks.before(async () => {
app = await scenario.prepare();
});
test('yarn test', async function (assert) {
let result = await app.execute('volta run npm -- run test');
assert.equal(result.exitCode, 0, result.output);
});
});
});

const EMBER_CLI_BUILD_JS = `
process.env.USE_EAI_BABEL_WATERMARK = 'true';
'use strict';
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
let app = new EmberApp(defaults, {
babel: {
plugins: [
function testTransform(babel) {
return {
visitor: {
CallExpression(path) {
let callee = path.get('callee');
if (!callee.isIdentifier()) {
return;
}
if (callee.node.name === 'TRANSPILE_TARGET') {
path.replaceWith(babel.types.stringLiteral("it woked"))
}
}
}
}
}
]
},
autoImport: {
skipBabel: [{
package: 'a-module-dependency',
semverRange: '*'
}],
allowAppImports: [
'lib/**'
]
}
});
return app.toTree();
};
`;

const APPLICATION_JS = `
import Controller from '@ember/controller';
import { computed } from '@ember-decorators/object';
import aModuleDependency from 'a-module-dependency';
export default class extends Controller {
@computed()
get moduleResult() {
return aModuleDependency();
}
}
`;

const BASIC_TEST_JS = `
import { module, test } from 'qunit';
import { visit } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
module('Acceptance | basic', function(hooks) {
setupApplicationTest(hooks);
test('visiting /basic', async function(assert) {
await visit('/');
assert.dom('[data-test-import-result]').hasText('module not transpiled with cleanBabelConfig');
});
});
`;
Loading

0 comments on commit bfe310a

Please sign in to comment.