Skip to content

Commit 95831dc

Browse files
committed
Add import option to babel-plugin-transform-jsx-to-htm
1 parent 0a7c406 commit 95831dc

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

packages/babel-plugin-transform-jsx-to-htm/index.mjs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,37 @@ import jsx from '@babel/plugin-syntax-jsx';
44
* @param {Babel} babel
55
* @param {object} [options]
66
* @param {string} [options.tag='html'] The tagged template "tag" function name to produce.
7+
* @param {string | boolean | object} [options.import=false] Import the tag automatically
78
* @param {string} [options.html=false] If `true`, output HTML-like instead of XML-like (no self-closing tags, etc).
89
*/
910
export default function jsxToHtmBabelPlugin({ types: t }, options = {}) {
10-
const tag = dottedIdentifier(options.tag || 'html');
11+
const tagString = options.tag || 'html';
12+
const tag = dottedIdentifier(tagString);
1113
const htmlOutput = !!options.html;
14+
const importDeclaration = tagImport(options.import || false);
15+
16+
function tagImport(imp) {
17+
if (imp === false) {
18+
return null;
19+
}
20+
const tagRoot = t.identifier(tagString.split('.')[0]);
21+
const { module, export: export_ } = typeof imp !== 'string' ? imp : {
22+
module: imp,
23+
export: null
24+
};
25+
26+
let specifier;
27+
if (export_ === '*') {
28+
specifier = t.importNamespaceSpecifier(tagRoot);
29+
}
30+
else if (export_ === 'default') {
31+
specifier = t.importDefaultSpecifier(tagRoot);
32+
}
33+
else {
34+
specifier = t.importSpecifier(tagRoot, export_ ? t.identifier(export_) : tagRoot);
35+
}
36+
return t.importDeclaration([specifier], t.stringLiteral(module));
37+
}
1238

1339
function dottedIdentifier(keypath) {
1440
const path = keypath.split('.');
@@ -147,7 +173,15 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) {
147173
name: 'transform-jsx-to-htm',
148174
inherits: jsx,
149175
visitor: {
150-
JSXElement(path) {
176+
Program: {
177+
exit(path, state) {
178+
if (state.get('jsxElement') && importDeclaration) {
179+
path.unshiftContainer('body', importDeclaration);
180+
}
181+
}
182+
},
183+
184+
JSXElement(path, state) {
151185
let quasisBefore = quasis.slice();
152186
let expressionsBefore = expressions.slice();
153187
let bufferBefore = buffer;
@@ -161,6 +195,8 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) {
161195
quasis = quasisBefore;
162196
expressions = expressionsBefore;
163197
buffer = bufferBefore;
198+
199+
state.set('jsxElement', true);
164200
}
165201
}
166202
};

test/babel-transform-jsx.test.mjs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ function compile(code, { plugins = [], ...options } = {}) {
55
return transform(code, {
66
babelrc: false,
77
configFile: false,
8-
sourceType: 'script',
8+
sourceType: 'module',
99
plugins: [
1010
...plugins,
1111
[transformJsxToHtmPlugin, options]
@@ -14,6 +14,50 @@ function compile(code, { plugins = [], ...options } = {}) {
1414
}
1515

1616
describe('babel-plugin-transform-jsx-to-htm', () => {
17+
describe('import', () => {
18+
test('import shortcut', () => {
19+
expect(
20+
compile(`(<div />);`, { import: 'htm/preact' })
21+
).toBe('import { html } from "htm/preact";\nhtml`<div/>`;');
22+
});
23+
24+
test('import shortcut, dotted tag', () => {
25+
expect(
26+
compile(`(<div />);`, { tag: 'html.bound', import: 'htm/preact' })
27+
).toBe('import { html } from "htm/preact";\nhtml.bound`<div/>`;');
28+
});
29+
30+
test('named import', () => {
31+
expect(
32+
compile(`(<div />);`, { import: { module: 'htm/preact', export: '$html' } })
33+
).toBe('import { $html as html } from "htm/preact";\nhtml`<div/>`;');
34+
});
35+
36+
test('named import, dotted tag', () => {
37+
expect(
38+
compile(`(<div />);`, { tag: 'html.bound', import: { module: 'htm/preact', export: '$html' } })
39+
).toBe('import { $html as html } from "htm/preact";\nhtml.bound`<div/>`;');
40+
});
41+
42+
test('default import', () => {
43+
expect(
44+
compile(`(<div />);`, { import: { module: 'htm/preact', export: 'default' } })
45+
).toBe('import html from "htm/preact";\nhtml`<div/>`;');
46+
});
47+
48+
test('namespace import', () => {
49+
expect(
50+
compile(`(<div />);`, { import: { module: 'htm/preact', export: '*' } })
51+
).toBe('import * as html from "htm/preact";\nhtml`<div/>`;');
52+
});
53+
54+
test('no import without JSX', () => {
55+
expect(
56+
compile(`false;`, { import: 'htm/preact' })
57+
).toBe('false;');
58+
});
59+
});
60+
1761
describe('elements and text', () => {
1862
test('single named element', () => {
1963
expect(

0 commit comments

Comments
 (0)