Skip to content

Commit 3e661e6

Browse files
authored
Merge pull request #144 from Lyrkan/add-preact-preset
Add Preact preset (with preact-compat support)
2 parents 6a241d9 + c0cb4ea commit 3e661e6

12 files changed

+291
-4
lines changed

index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,26 @@ const publicApi = {
406406
return this;
407407
},
408408

409+
/**
410+
* If enabled, a Preact preset will be applied to
411+
* the generated Webpack configuration.
412+
*
413+
* Encore.enablePreactPreset()
414+
*
415+
* If you wish to also use preact-compat (https://github.com/developit/preact-compat)
416+
* you can enable it by setting the "preactCompat" option to true:
417+
*
418+
* Encore.enablePreactPreset({ preactCompat: true })
419+
*
420+
* @param {object} options
421+
* @returns {exports}
422+
*/
423+
enablePreactPreset(options = {}) {
424+
webpackConfig.enablePreactPreset(options);
425+
426+
return this;
427+
},
428+
409429
/**
410430
* Call this if you plan on loading TypeScript files.
411431
*

lib/WebpackConfig.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ class WebpackConfig {
5454
this.providedVariables = {};
5555
this.babelConfigurationCallback = function() {};
5656
this.useReact = false;
57+
this.usePreact = false;
58+
this.preactOptions = {
59+
preactCompat: false
60+
};
5761
this.useVueLoader = false;
5862
this.vueLoaderOptionsCallback = () => {};
5963
this.loaders = [];
@@ -269,6 +273,18 @@ class WebpackConfig {
269273
this.useReact = true;
270274
}
271275

276+
enablePreactPreset(options = {}) {
277+
this.usePreact = true;
278+
279+
for (const optionKey of Object.keys(options)) {
280+
if (!(optionKey in this.preactOptions)) {
281+
throw new Error(`Invalid option "${optionKey}" passed to enablePreactPreset(). Valid keys are ${Object.keys(this.preactOptions).join(', ')}`);
282+
}
283+
284+
this.preactOptions[optionKey] = options[optionKey];
285+
}
286+
}
287+
272288
enableTypeScriptLoader(callback = () => {}) {
273289
this.useTypeScriptLoader = true;
274290

lib/config-generator.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ class ConfigGenerator {
8181
config.resolve.alias['vue$'] = 'vue/dist/vue.esm.js';
8282
}
8383

84+
if (this.webpackConfig.usePreact && this.webpackConfig.preactOptions.preactCompat) {
85+
config.resolve.alias['react'] = 'preact-compat';
86+
config.resolve.alias['react-dom'] = 'preact-compat';
87+
}
88+
8489
return config;
8590
}
8691

lib/features.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ const features = {
3636
packages: ['babel-preset-react'],
3737
description: 'process React JS files'
3838
},
39+
preact: {
40+
method: 'enablePreactPreset()',
41+
packages: ['babel-plugin-transform-react-jsx'],
42+
description: 'process Preact JS files'
43+
},
3944
typescript: {
4045
method: 'enableTypeScriptLoader()',
4146
packages: ['typescript', 'ts-loader'],

lib/loaders/babel.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,23 @@ module.exports = {
5050
babelConfig.presets.push('react');
5151
}
5252

53+
if (webpackConfig.usePreact) {
54+
loaderFeatures.ensurePackagesExist('preact');
55+
56+
if (webpackConfig.preactOptions.preactCompat) {
57+
// If preact-compat is enabled tell babel to
58+
// transform JSX into React.createElement calls.
59+
babelConfig.plugins.push(['transform-react-jsx']);
60+
} else {
61+
// If preact-compat is disabled tell babel to
62+
// transform JSX into Preact h() calls.
63+
babelConfig.plugins.push([
64+
'transform-react-jsx',
65+
{ 'pragma': 'h' }
66+
]);
67+
}
68+
}
69+
5370
// allow for babel config to be controlled
5471
webpackConfig.babelConfigurationCallback.apply(
5572
// use babelConfig as the this variable

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
},
5050
"devDependencies": {
5151
"autoprefixer": "^6.7.7",
52+
"babel-plugin-transform-react-jsx": "^6.24.1",
5253
"babel-preset-react": "^6.23.0",
5354
"chai": "^3.5.0",
5455
"chai-fs": "^1.0.0",
@@ -63,6 +64,8 @@
6364
"node-sass": "^4.5.3",
6465
"nsp": "^2.6.3",
6566
"postcss-loader": "^1.3.3",
67+
"preact": "^8.2.1",
68+
"preact-compat": "^3.17.0",
6669
"sass-loader": "^6.0.3",
6770
"sinon": "^2.3.4",
6871
"ts-loader": "^2.1.0",

test/WebpackConfig.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,35 @@ describe('WebpackConfig object', () => {
394394
});
395395
});
396396

397+
describe('enablePreactPreset', () => {
398+
it('Without preact-compat', () => {
399+
const config = createConfig();
400+
config.enablePreactPreset();
401+
402+
expect(config.usePreact).to.be.true;
403+
expect(config.preactOptions.preactCompat).to.be.false;
404+
});
405+
406+
it('With preact-compat', () => {
407+
const config = createConfig();
408+
config.enablePreactPreset({
409+
preactCompat: true
410+
});
411+
412+
expect(config.usePreact).to.be.true;
413+
expect(config.preactOptions.preactCompat).to.be.true;
414+
});
415+
416+
it('With an invalid option', () => {
417+
const config = createConfig();
418+
expect(() => {
419+
config.enablePreactPreset({
420+
foo: true
421+
});
422+
}).to.throw('Invalid option "foo"');
423+
});
424+
});
425+
397426
describe('enableTypeScriptLoader', () => {
398427
it('Calling method sets it', () => {
399428
const config = createConfig();

test/config-generator.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,4 +529,32 @@ describe('The config-generator function', () => {
529529
expect(fontsRule.options.name).to.equal('[name].bar.[ext]');
530530
});
531531
});
532+
533+
describe('Test preact preset', () => {
534+
describe('Without preact-compat', () => {
535+
it('enablePreactPreset() does not add aliases to use preact-compat', () => {
536+
const config = createConfig();
537+
config.outputPath = '/tmp/public/build';
538+
config.setPublicPath('/build/');
539+
config.enablePreactPreset();
540+
541+
const actualConfig = configGenerator(config);
542+
expect(actualConfig.resolve.alias).to.not.include.keys('react', 'react-dom');
543+
});
544+
});
545+
546+
describe('With preact-compat', () => {
547+
it('enablePreactPreset({ preactCompat: true }) adds aliases to use preact-compat', () => {
548+
const config = createConfig();
549+
config.outputPath = '/tmp/public/build';
550+
config.setPublicPath('/build/');
551+
config.enablePreactPreset({ preactCompat: true });
552+
553+
const actualConfig = configGenerator(config);
554+
expect(actualConfig.resolve.alias).to.include.keys('react', 'react-dom');
555+
expect(actualConfig.resolve.alias['react']).to.equal('preact-compat');
556+
expect(actualConfig.resolve.alias['react-dom']).to.equal('preact-compat');
557+
});
558+
});
559+
});
532560
});

test/functional.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,40 @@ module.exports = {
630630
});
631631
});
632632

633+
it('When enabled, preact JSX is transformed without preact-compat!', (done) => {
634+
const config = createWebpackConfig('www/build', 'dev');
635+
config.setPublicPath('/build');
636+
config.addEntry('main', './js/CoolReactComponent.jsx');
637+
config.enablePreactPreset();
638+
639+
testSetup.runWebpack(config, (webpackAssert) => {
640+
// check that babel transformed the JSX
641+
webpackAssert.assertOutputFileContains(
642+
'main.js',
643+
'var hiGuys = h('
644+
);
645+
646+
done();
647+
});
648+
});
649+
650+
it('When enabled, preact JSX is transformed with preact-compat!', (done) => {
651+
const config = createWebpackConfig('www/build', 'dev');
652+
config.setPublicPath('/build');
653+
config.addEntry('main', './js/CoolReactComponent.jsx');
654+
config.enablePreactPreset({ preactCompat: true });
655+
656+
testSetup.runWebpack(config, (webpackAssert) => {
657+
// check that babel transformed the JSX
658+
webpackAssert.assertOutputFileContains(
659+
'main.js',
660+
'React.createElement'
661+
);
662+
663+
done();
664+
});
665+
});
666+
633667
it('When configured, TypeScript is compiled!', (done) => {
634668
const config = createWebpackConfig('www/build', 'dev');
635669
config.setPublicPath('/build');

test/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@ describe('Public API', () => {
179179

180180
});
181181

182+
describe('enablePreactPreset', () => {
183+
184+
it('must return the API object', () => {
185+
const returnedValue = api.enablePreactPreset();
186+
expect(returnedValue).to.equal(api);
187+
});
188+
189+
});
190+
182191
describe('enableTypeScriptLoader', () => {
183192

184193
it('must return the API object', () => {

test/loaders/babel.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,40 @@ describe('loaders/babel', () => {
6565
// foo is also still there, not overridden
6666
expect(actualLoaders[0].options.presets).to.include('foo');
6767
});
68+
69+
it('getLoaders() with preact', () => {
70+
const config = createConfig();
71+
config.enablePreactPreset();
72+
73+
config.configureBabel(function(babelConfig) {
74+
babelConfig.plugins.push('foo');
75+
});
76+
77+
const actualLoaders = babelLoader.getLoaders(config);
78+
79+
// transform-react-jsx & foo
80+
expect(actualLoaders[0].options.plugins).to.have.lengthOf(2);
81+
expect(actualLoaders[0].options.plugins).to.deep.include.members([
82+
['transform-react-jsx', { pragma: 'h' }],
83+
'foo'
84+
]);
85+
});
86+
87+
it('getLoaders() with preact and preact-compat', () => {
88+
const config = createConfig();
89+
config.enablePreactPreset({ preactCompat: true });
90+
91+
config.configureBabel(function(babelConfig) {
92+
babelConfig.plugins.push('foo');
93+
});
94+
95+
const actualLoaders = babelLoader.getLoaders(config);
96+
97+
// transform-react-jsx & foo
98+
expect(actualLoaders[0].options.plugins).to.have.lengthOf(2);
99+
expect(actualLoaders[0].options.plugins).to.deep.include.members([
100+
['transform-react-jsx'],
101+
'foo'
102+
]);
103+
});
68104
});

0 commit comments

Comments
 (0)