Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Encore.enableBabelTypeScriptPreset() to "compile" TypeScript with Babel #694

Merged
merged 7 commits into from
Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions fixtures/js/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import render = require('./render');
import render from './render';

render();
render();
5 changes: 3 additions & 2 deletions fixtures/js/render.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
function render() {
document.getElementById('app').innerHTML = "<h1>Welcome to Your TypeScript App</h1>";
const html: string = "<h1>Welcome to Your TypeScript App</h1>";
document.getElementById('app').innerHTML = html;
}

export = render;
export default render;
2 changes: 1 addition & 1 deletion fixtures/js/render2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ function render() {
document.getElementById('app').innerHTML = "<h1>Welcome to Your TypeScript App</h1>";
}

export = render;
export default render;
6 changes: 4 additions & 2 deletions fixtures/js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"compilerOptions": {}
}
"compilerOptions": {
"allowSyntheticDefaultImports": true
}
}
38 changes: 37 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,7 @@ class Encore {

/**
* If enabled, a Preact preset will be applied to
* the generated Webpack configuration.
* the generated Webpack and Babel configuration.
*
* ```
* Encore.enablePreactPreset()
Expand Down Expand Up @@ -1044,6 +1044,42 @@ class Encore {
return this;
}


/**
* If enabled, a TypeScript preset will be applied to
* the generated Webpack and Babel configuration.
*
* ```
* Encore.enableBabelTypeScriptPreset()
* ```
*
* This method let Babel handle your TypeScript code
* and can not be used with `Encore.enableTypeScriptLoader()`
* or `Encore.enableForkedTypeScriptTypesChecking()`.
*
* Since all types are removed by Babel,
* you must run `tsc --noEmit` yourself for types checking.
*
* The Babel TypeScript preset can be configured,
* see https://babeljs.io/docs/en/babel-preset-typescript#options
* for available options.
*
* For example:
* ```
* Encore.enableBabelTypeScriptPreset({
* isTSX: true
* })
* ```
*
* @param {object} options
* @returns {Encore}
*/
enableBabelTypeScriptPreset(options) {
webpackConfig.enableBabelTypeScriptPreset(options);

return this;
}

/**
* If enabled, the Vue.js loader is enabled.
*
Expand Down
22 changes: 22 additions & 0 deletions lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class WebpackConfig {
this.useEslintLoader = false;
this.useTypeScriptLoader = false;
this.useForkedTypeScriptTypeChecking = false;
this.useBabelTypeScriptPreset = false;
this.useWebpackNotifier = false;
this.useHandlebarsLoader = false;

Expand All @@ -130,6 +131,7 @@ class WebpackConfig {
useBuiltIns: false,
corejs: null,
};
this.babelTypeScriptPresetOptions = {};
this.vueOptions = {
useJsx: false,
};
Expand Down Expand Up @@ -647,6 +649,10 @@ class WebpackConfig {
}

enableTypeScriptLoader(callback = () => {}) {
if (this.useBabelTypeScriptPreset) {
throw new Error('Encore.enableTypeScriptLoader() can not be called when Encore.enableBabelTypeScriptPreset() has been called.');
}

this.useTypeScriptLoader = true;

if (typeof callback !== 'function') {
Expand All @@ -657,6 +663,9 @@ class WebpackConfig {
}

enableForkedTypeScriptTypesChecking(forkedTypeScriptTypesCheckOptionsCallback = () => {}) {
if (this.useBabelTypeScriptPreset) {
throw new Error('Encore.enableForkedTypeScriptTypesChecking() can not be called when Encore.enableBabelTypeScriptPreset() has been called.');
}

if (typeof forkedTypeScriptTypesCheckOptionsCallback !== 'function') {
throw new Error('Argument 1 to enableForkedTypeScriptTypesChecking() must be a callback function.');
Expand All @@ -667,6 +676,19 @@ class WebpackConfig {
forkedTypeScriptTypesCheckOptionsCallback;
}

enableBabelTypeScriptPreset(options = {}) {
if (this.useTypeScriptLoader) {
throw new Error('Encore.enableBabelTypeScriptPreset() can not be called when Encore.enableTypeScriptLoader() has been called.');
}

if (this.useForkedTypeScriptTypeChecking) {
throw new Error('Encore.enableBabelTypeScriptPreset() can not be called when Encore.enableForkedTypeScriptTypesChecking() has been called.');
}

this.useBabelTypeScriptPreset = true;
this.babelTypeScriptPresetOptions = options;
}

enableVueLoader(vueLoaderOptionsCallback = () => {}, vueOptions = {}) {
this.useVueLoader = true;

Expand Down
3 changes: 1 addition & 2 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,7 @@ class ConfigGenerator {

let rules = [
applyRuleConfigurationCallback('javascript', {
// match .js and .jsx
test: /\.jsx?$/,
test: babelLoaderUtil.getTest(this.webpackConfig),
exclude: this.webpackConfig.babelOptions.exclude,
use: babelLoaderUtil.getLoaders(this.webpackConfig)
}),
Expand Down
9 changes: 9 additions & 0 deletions lib/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ const features = {
],
description: 'check TypeScript types in a separate process'
},
'typescript-babel': {
method: 'enableBabelTypeScriptPreset',
packages: [
{ name: 'typescript' },
{ name: '@babel/preset-typescript', enforce_version: true },
{ name: '@babel/plugin-proposal-class-properties', enforce_version: true },
],
description: 'process TypeScript files with Babel'
},
vue: {
method: 'enableVueLoader()',
// vue is needed so the end-user can do things
Expand Down
23 changes: 23 additions & 0 deletions lib/loaders/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ module.exports = {
plugins: ['@babel/plugin-syntax-dynamic-import']
});

if (webpackConfig.useBabelTypeScriptPreset) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('typescript-babel');

babelConfig.presets.push(['@babel/preset-typescript', webpackConfig.babelTypeScriptPresetOptions]);
babelConfig.plugins.push('@babel/plugin-proposal-class-properties');
}

if (webpackConfig.useReact) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('react');

Expand Down Expand Up @@ -95,5 +102,21 @@ module.exports = {
options: babelConfig
}
];
},

/**
* @param {WebpackConfig} webpackConfig
* @return {RegExp} to use for eslint-loader `test` rule
*/
getTest(webpackConfig) {
const extensions = [
'jsx?', // match .js and .jsx
];

if (webpackConfig.useBabelTypeScriptPreset) {
extensions.push('tsx?'); // match .ts and .tsx
}

return new RegExp(`\\.(${extensions.join('|')})$`);
}
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
},
"devDependencies": {
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.0.0",
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0-beta.3",
"@vue/babel-preset-jsx": "^1.0.0-beta.3",
"autoprefixer": "^8.5.0",
Expand Down
47 changes: 47 additions & 0 deletions test/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,15 @@ describe('WebpackConfig object', () => {
config.enableTypeScriptLoader('FOO');
}).to.throw('must be a callback function');
});

it('TypeScript can not be compiled by ts-loader if Babel is already handling TypeScript', () => {
const config = createConfig();
config.enableBabelTypeScriptPreset();

expect(function() {
config.enableTypeScriptLoader();
}).to.throw('Encore.enableTypeScriptLoader() can not be called when Encore.enableBabelTypeScriptPreset() has been called.');
});
});

describe('enableForkedTypeScriptTypesChecking', () => {
Expand All @@ -891,6 +900,44 @@ describe('WebpackConfig object', () => {
config.enableForkedTypeScriptTypesChecking('FOO');
}).to.throw('must be a callback function');
});

it('TypeScript can not be compiled by Babel if forked types checking is enabled', () => {
const config = createConfig();
config.enableBabelTypeScriptPreset();

expect(function() {
config.enableForkedTypeScriptTypesChecking();
}).to.throw('Encore.enableForkedTypeScriptTypesChecking() can not be called when Encore.enableBabelTypeScriptPreset() has been called.');
});
});

describe('enableBabelTypeScriptPreset', () => {
it('TypeScript can not be compiled by Babel if ts-loader is already enabled', () => {
const config = createConfig();
config.enableTypeScriptLoader();

expect(function() {
config.enableBabelTypeScriptPreset();
}).to.throw('Encore.enableBabelTypeScriptPreset() can not be called when Encore.enableTypeScriptLoader() has been called.');
});

it('TypeScript can not be compiled by Babel if ts-loader is already enabled', () => {
const config = createConfig();
config.enableForkedTypeScriptTypesChecking();

expect(function() {
config.enableBabelTypeScriptPreset();
}).to.throw('Encore.enableBabelTypeScriptPreset() can not be called when Encore.enableForkedTypeScriptTypesChecking() has been called.');
});

it('Options should be defined', () => {
const config = createConfig();
const options = { isTSX: true };

config.enableBabelTypeScriptPreset(options);

expect(config.babelTypeScriptPresetOptions).to.equal(options);
});
});

describe('enableVueLoader', () => {
Expand Down
14 changes: 7 additions & 7 deletions test/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ describe('The config-generator function', () => {

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
const jsRule = findRule(/\.(jsx?)$/, actualConfig.module.rules);

// check for the default env preset only
expect(JSON.stringify(jsRule.use[0].options.presets)).contains('@babel/preset-env');
Expand Down Expand Up @@ -932,7 +932,7 @@ describe('The config-generator function', () => {

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
const jsRule = findRule(/\.(jsx?)$/, actualConfig.module.rules);
expect(String(jsRule.exclude)).to.equal(String(/(node_modules|bower_components)/));

const babelLoader = jsRule.use.find(loader => loader.loader === 'babel-loader');
Expand All @@ -951,7 +951,7 @@ describe('The config-generator function', () => {

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
const jsRule = findRule(/\.(jsx?)$/, actualConfig.module.rules);
expect(String(jsRule.exclude)).to.equal(String(/foo/));
});

Expand All @@ -966,7 +966,7 @@ describe('The config-generator function', () => {

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
const jsRule = findRule(/\.(jsx?)$/, actualConfig.module.rules);
expect(jsRule.exclude).to.be.a('Function');
expect(jsRule.exclude(path.join('test', 'node_modules', 'foo', 'index.js'))).to.be.false;
expect(jsRule.exclude(path.join('test', 'node_modules', 'bar', 'index.js'))).to.be.true;
Expand All @@ -984,7 +984,7 @@ describe('The config-generator function', () => {

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
const jsRule = findRule(/\.(jsx?)$/, actualConfig.module.rules);
const babelLoader = jsRule.use.find(loader => loader.loader === 'babel-loader');
const babelEnvPreset = babelLoader.options.presets.find(([name]) => name === '@babel/preset-env');
expect(babelEnvPreset[1].useBuiltIns).to.equal('usage');
Expand All @@ -1001,7 +1001,7 @@ describe('The config-generator function', () => {

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
const jsRule = findRule(/\.(jsx?)$/, actualConfig.module.rules);
const babelLoader = jsRule.use.find(loader => loader.loader === 'babel-loader');
const babelEnvPreset = babelLoader.options.presets.find(([name]) => name === '@babel/preset-env');
expect(babelEnvPreset[1].useBuiltIns).to.equal(false);
Expand All @@ -1018,7 +1018,7 @@ describe('The config-generator function', () => {

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
const jsRule = findRule(/\.(jsx?)$/, actualConfig.module.rules);
const babelLoader = jsRule.use.find(loader => loader.loader === 'babel-loader');
const babelEnvPreset = babelLoader.options.presets.find(([name]) => name === '@babel/preset-env');
expect(babelEnvPreset[1].useBuiltIns).to.equal('usage');
Expand Down
29 changes: 29 additions & 0 deletions test/functional.js
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,35 @@ module.exports = {
}).to.throw('wrong `tsconfig` path in fork plugin configuration (should be a relative or absolute path)');
});

it('TypeScript can be compiled by Babel', (done) => {
const config = createWebpackConfig('www/build', 'dev');
config.setPublicPath('/build');
config.addEntry('main', ['./js/render.ts', './js/index.ts']);
config.enableBabelTypeScriptPreset();

testSetup.runWebpack(config, (webpackAssert) => {
// check that babel-loader transformed the ts file
webpackAssert.assertOutputFileContains(
'main.js',
'document.getElementById(\'app\').innerHTML =',
);

testSetup.requestTestPage(
path.join(config.getContext(), 'www'),
[
'build/runtime.js',
'build/main.js',
],
(browser) => {

// assert that the ts module rendered
browser.assert.text('#app h1', 'Welcome to Your TypeScript App');
done();
},
);
});
});

it('When configured, Handlebars is compiled', (done) => {
const config = createWebpackConfig('www/build', 'dev');
config.setPublicPath('/build');
Expand Down
Loading