Skip to content

Commit

Permalink
Merge pull request #1199 from wondersloth/medwards/babel-template-tra…
Browse files Browse the repository at this point in the history
…nsform-plugin

Add babel plugin for preprocessing templates with ast transforms
  • Loading branch information
ef4 authored Jun 9, 2022
2 parents 0a55dd6 + cac2720 commit 871bb24
Show file tree
Hide file tree
Showing 13 changed files with 313 additions and 19 deletions.
16 changes: 8 additions & 8 deletions PORTING-ADDONS-TO-V2.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,7 @@ The steps:
```json
{
"private": true,
"workspaces": [
"addon",
"test-app"
]
"workspaces": ["addon", "test-app"]
}
```

Expand Down Expand Up @@ -219,6 +216,7 @@ Now that we've separated the test-app and docs app concerns from the addon, we c
`yarn add --dev @embroider/addon-dev rollup @rollup/plugin-babel @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators`

6. Grab the [example babel config](https://github.com/embroider-build/embroider/blob/main/packages/addon-dev/sample-babel.config.json) and save it as `addon/babel.config.json`
- If you addon requires template transforms in order to publish to a shareable format. Apply transforms using the `@embroider/addon-dev/template-transform-plugin`. View how to use this in the [example babel.config.js](https://github.com/embroider-build/embroider/blob/main/packages/addon-dev/sample-babel.config.js)
7. Grab the [example rollup config](https://github.com/embroider-build/embroider/blob/main/packages/addon-dev/sample-rollup.config.js) and save it as `addon/rollup.config.js`.
8. Identify your **app reexports**. This is the list of modules from your addon that get reexported by files in the `addon/app` directory.
9. Delete the `addon/app` directory. You aren't going to need it anymore.
Expand All @@ -230,10 +228,12 @@ Now that we've separated the test-app and docs app concerns from the addon, we c
11. Still editing `addon/rollup.config.js`, customize the `appReexports` to match all your **app reexports** as identified above.
12. Delete your `addon/index.js` file.
13. Create a new `addon/addon-main.js` file (this replaces `addon/index.js`) with this exact content:
```js
const { addonV1Shim } = require('@embroider/addon-shim');
module.exports = addonV1Shim(__dirname);
```

```js
const { addonV1Shim } = require('@embroider/addon-shim');
module.exports = addonV1Shim(__dirname);
```

14. In your `addon/.eslintrc.js`, replace "index.js" with "addon-main.js" so that our new file will lint correctly as Node code.
15. In your `addon/package.json`, add these things:
```js
Expand Down
3 changes: 3 additions & 0 deletions packages/addon-dev/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
/src/**/*.js
/src/**/*.d.ts
/src/**/*.map
/tests/**/*.js
/tests/**/*.d.ts
/tests/**/*.map

# dependencies
/node_modules/
Expand Down
6 changes: 6 additions & 0 deletions packages/addon-dev/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
testEnvironment: 'node',
testMatch: [
'<rootDir>/tests/**/*.test.js',
],
};
20 changes: 18 additions & 2 deletions packages/addon-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"exports": {
"./template-colocation-plugin": "./src/template-colocation-plugin.js",
"./template-transform-plugin": "./src/template-transform-plugin.js",
"./rollup": "./src/rollup.js"
},
"files": [
Expand All @@ -22,8 +23,12 @@
"src/**/*.d.ts",
"src/**/*.js.map"
],
"scripts": {
"prepare": "tsc",
"test": "jest"
},
"dependencies": {
"@embroider/shared-internals": "^1.7.1",
"@embroider/core": "^1.7.1",
"@rollup/pluginutils": "^4.1.1",
"assert-never": "^1.2.1",
"fs-extra": "^10.0.0",
Expand All @@ -34,10 +39,21 @@
"yargs": "^17.0.1"
},
"devDependencies": {
"@embroider/test-support": "0.36.0",
"@glimmer/syntax": "^0.84.2",
"@types/fs-extra": "^9.0.12",
"@types/minimatch": "^3.0.4",
"@types/yargs": "^17.0.3",
"rollup": "^2.58.0"
"rollup": "^2.58.0",
"tmp": "^0.1.0"
},
"peerDependencies": {
"ember-source": "*"
},
"peerDependenciesMeta": {
"ember-source": {
"optional": true
}
},
"engines": {
"node": "12.* || 14.* || >= 16"
Expand Down
25 changes: 25 additions & 0 deletions packages/addon-dev/sample-babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Some addons need to transform their templates before they have a portable format.
// In "classic" builds this was done at the application. In embroider it should
// be done during the addon build.
const someAstTransformPlugin = require('./some-ast-transform-plugin');

// The `@embroider/addon-dev/template-transform-plugin` has the following options:
// `options.astTransforms` - an array of functions or paths to preprocess the GlimmerAST
// `options.compilerPath` - Optional: Defaults to `ember-source/dist/ember-template-compiler`

module.exports = {
plugins: [
'@embroider/addon-dev/template-colocation-plugin',
[
'@embroider/addon-dev/template-transform-plugin',
{
astTransforms: [
someAstTransformPlugin,
'./path/to/another-template-transform-plugin',
],
},
],
['@babel/plugin-proposal-decorators', { legacy: true }],
'@babel/plugin-proposal-class-properties',
],
};
2 changes: 1 addition & 1 deletion packages/addon-dev/src/rollup-addon-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
emberVirtualPeerDeps,
packageName,
templateCompilationModules,
} from '@embroider/shared-internals';
} from '@embroider/core';

const compilationModules = new Set(
templateCompilationModules.map((m) => m.module)
Expand Down
2 changes: 1 addition & 1 deletion packages/addon-dev/src/rollup-hbs-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
ResolvedId,
} from 'rollup';
import { readFileSync } from 'fs';
import { hbsToJS } from '@embroider/shared-internals';
import { hbsToJS } from '@embroider/core';
import assertNever from 'assert-never';
import { parse as pathParse } from 'path';

Expand Down
11 changes: 7 additions & 4 deletions packages/addon-dev/src/template-colocation-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export {
default,
Options,
} from '@embroider/shared-internals/src/template-colocation-plugin';
import {
templateColocationPlugin,
Options as TemplateColocationPluginOptions,
} from '@embroider/core';

export { TemplateColocationPluginOptions as Options };
export default templateColocationPlugin;
42 changes: 42 additions & 0 deletions packages/addon-dev/src/template-transform-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import make from '@embroider/core/src/babel-plugin-stage1-inline-hbs';
import { TemplateCompiler, TemplateCompilerParams } from '@embroider/core';
import { getEmberExports } from '@embroider/core/src/load-ember-template-compiler';

export type TemplateTransform = () => { name: string; visitor: {} };
export type TemplateTransformPlugin = TemplateTransform | string;
export interface Options {
// An array of either Glimmer AST plugins or paths that can be resolved to a plugin.
astTransforms?: TemplateTransformPlugin[];
// Defaults to 'ember-source/dist/ember-template-compiler'
compilerPath?: string;
}

function resolvePlugins(plugins: TemplateTransformPlugin[]) {
return plugins.map((somePlugin: TemplateTransformPlugin) => {
// If it's a string attempt to resolve the path to a module.
return typeof somePlugin === 'string'
? require(somePlugin) // eslint-disable-line @typescript-eslint/no-require-imports
: somePlugin;
});
}

export default make((options: Options) => {
let {
astTransforms: somePlugins = [],
compilerPath = 'ember-source/dist/ember-template-compiler',
} = options;

compilerPath = require.resolve(compilerPath);

const astTransforms: TemplateTransform[] = resolvePlugins(somePlugins);

const params: TemplateCompilerParams = {
EmberENV: {},
loadEmberTemplateCompiler: () => getEmberExports(compilerPath),
plugins: {
ast: astTransforms,
},
};

return new TemplateCompiler(params);
});
150 changes: 150 additions & 0 deletions packages/addon-dev/tests/template-transform-plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {
allBabelVersions,
emberTemplateCompilerPath,
} from '@embroider/test-support';
import {
TemplateTransformPlugin,
Options,
} from '../src/template-transform-plugin';
import { hbsToJS } from '@embroider/core';
import { AST } from '@glimmer/syntax';
import { join } from 'path';
import tmp from 'tmp';
import { writeFileSync } from 'fs-extra';

describe('template-transform-plugin', () => {
jest.setTimeout(120000);

const templateTransformBabelPlugin = join(
__dirname,
'../src/template-transform-plugin.js'
);

let plugins: any = [];

function reverseTransform() {
return {
name: 'reverse-transform',
visitor: {
ElementNode(node: AST.ElementNode) {
node.tag = node.tag.split('').reverse().join('');
},
},
};
}

function setupPlugins(options?: {
astTransforms: TemplateTransformPlugin[];
}) {
const opts: Options = {
astTransforms: options?.astTransforms,
compilerPath: emberTemplateCompilerPath(),
};
plugins = [[templateTransformBabelPlugin, opts]];
}

allBabelVersions({
babelConfig() {
return {
plugins,
};
},
createTests(transform) {
afterEach(function () {
plugins = undefined;
});

test('no-op', () => {
setupPlugins();
const code = hbsToJS('Hello {{@phrase}}');
let output = transform(code);
expect(output).toMatch(
/import { hbs } from ['"]ember-cli-htmlbars['"];/
);
expect(output).toMatch(
/export default hbs\(['"]Hello {{@phrase}}['"]\);/
);
});

test('options.astTransforms empty array', () => {
setupPlugins({
astTransforms: [],
});
const code = hbsToJS('Hello {{@phrase}}');
let output = transform(code);
expect(output).toMatch(
/import { hbs } from ['"]ember-cli-htmlbars['"];/
);
expect(output).toMatch(
/export default hbs\(['"]Hello {{@phrase}}['"]\);/
);
});
test('options.astTransforms function', () => {
setupPlugins({
astTransforms: [reverseTransform],
});

const code = hbsToJS('<span>{{@phrase}}</span>');
let output = transform(code);
expect(output).toMatch(
/import { hbs } from ['"]ember-cli-htmlbars['"];/
);
expect(output).toMatch(
/export default hbs\(['"]\<naps\>{{@phrase}}\<\/naps\>['"]\);/
);
});

test('options.astTransforms path', () => {
const someFile = tmp.fileSync();

const contents = `module.exports = function reverseTransform() {
return {
name: 'reverse-transform',
visitor: {
ElementNode(node) {
node.tag = node.tag.split('').reverse().join('');
},
},
};
}`;

writeFileSync(someFile.name, contents, 'utf8');

setupPlugins({
astTransforms: [someFile.name],
});

const code = hbsToJS('<span>{{@phrase}}</span>');

let output = transform(code);

expect(output).toMatch(
/import { hbs } from ['"]ember-cli-htmlbars['"];/
);
expect(output).toMatch(
/export default hbs\(['"]\<naps\>{{@phrase}}\<\/naps\>['"]\);/
);

someFile.removeCallback();
});

test('ember-cli-htmlbars alias import name', () => {
setupPlugins({
astTransforms: [reverseTransform],
});

const code = `import { hbs as render } from 'ember-cli-htmlbars';
export default render('<span>{{@phrase}}</span>');`;

let output = transform(code);

expect(output).toMatch(
/import { hbs as render } from ['"]ember-cli-htmlbars['"];/
);
expect(output).toMatch(
/export default render\(['"]\<naps\>{{@phrase}}\<\/naps\>['"]\);/
);
});
},
});
});
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"./src/messages": "./src/messages.js",
"./src/babel-plugin-inline-hbs": "./src/babel-plugin-inline-hbs.js",
"./src/babel-plugin-stage1-inline-hbs": "./src/babel-plugin-stage1-inline-hbs.js",
"./src/mini-modules-polyfill": "./src/mini-modules-polyfill.js",
"./src/load-ember-template-compiler": "./src/load-ember-template-compiler.js"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/shared-internals/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ export { default as tmpdir } from './tmpdir';
export * from './ember-cli-models';
export * from './ember-standard-modules';
export { hbsToJS } from './hbs-to-js';
export {
default as templateColocationPlugin,
Options as TemplateColocationPluginOptions,
} from './template-colocation-plugin';
Loading

0 comments on commit 871bb24

Please sign in to comment.