Skip to content

Commit 6b85366

Browse files
committed
Add Preact preset (with preact-compat support)
1 parent e3fbf67 commit 6b85366

File tree

12 files changed

+272
-4
lines changed

12 files changed

+272
-4
lines changed

index.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,27 @@ const publicApi = {
387387
return this;
388388
},
389389

390+
/**
391+
* If enabled, a Preact preset will be applied to
392+
* the generated Webpack configuration.
393+
*
394+
* Encore.enablePreactPreset()
395+
*
396+
* If you wish to also use preact-compat (https://github.com/developit/preact-compat)
397+
* call that method with its first parameter set
398+
* to true:
399+
*
400+
* Encore.enablePreactPreset(true)
401+
*
402+
* @param {boolean} usePreactCompat
403+
* @returns {exports}
404+
*/
405+
enablePreactPreset(usePreactCompat = false) {
406+
webpackConfig.enablePreactPreset(usePreactCompat);
407+
408+
return this;
409+
},
410+
390411
/**
391412
* Call this if you plan on loading TypeScript files.
392413
*

lib/WebpackConfig.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class WebpackConfig {
5353
this.providedVariables = {};
5454
this.babelConfigurationCallback = function() {};
5555
this.useReact = false;
56+
this.usePreact = false;
57+
this.usePreactCompat = false;
5658
this.useVueLoader = false;
5759
this.vueLoaderOptionsCallback = () => {};
5860
this.loaders = [];
@@ -247,6 +249,11 @@ class WebpackConfig {
247249
this.useReact = true;
248250
}
249251

252+
enablePreactPreset(usePreactCompat = false) {
253+
this.usePreact = true;
254+
this.usePreactCompat = usePreactCompat;
255+
}
256+
250257
enableTypeScriptLoader(callback = () => {}) {
251258
this.useTypeScriptLoader = true;
252259

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.usePreactCompat) {
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.usePreactCompat) {
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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,24 @@ describe('WebpackConfig object', () => {
364364
});
365365
});
366366

367+
describe('enablePreactPreset', () => {
368+
it('Without preact-compat', () => {
369+
const config = createConfig();
370+
config.enablePreactPreset();
371+
372+
expect(config.usePreact).to.be.true;
373+
expect(config.usePreactCompat).to.be.false;
374+
});
375+
376+
it('With preact-compat', () => {
377+
const config = createConfig();
378+
config.enablePreactPreset(true);
379+
380+
expect(config.usePreact).to.be.true;
381+
expect(config.usePreactCompat).to.be.true;
382+
});
383+
});
384+
367385
describe('enableTypeScriptLoader', () => {
368386
it('Calling method sets it', () => {
369387
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(true) adds aliases to use preact-compat', () => {
548+
const config = createConfig();
549+
config.outputPath = '/tmp/public/build';
550+
config.setPublicPath('/build/');
551+
config.enablePreactPreset(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(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(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)