Babel not transpiling imported node_modules to ES5 - includes ES2015 syntax - javascript

My babel+webpack config works fine, but the resulting bundle isn't runnable in IE11 as it contains const declarations. I thought having the es2015 preset was enough to fix this? Running $(npm bin)/babel test/some-es2015.js produces strict ES5.1 code, so Babel seems to work, but the actual code that borks in IE11 is in modules imported from node_modules.
When grepping for 'const ' in my resulting bundle I get certain lines like this (the eval is due to eval source mapping btw):
eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst validator = __webpack_require__(/*! validator */ \"./node_modules/tcomb-additional-types/node_modules/validator/index.js\");\nconst t = __webpack_require__(/*! tcomb */ \"./node_modules/tcomb/index.js\");\nconst IP = t.refinement(t.String, validator.isIP);\nexports.IP = IP;\nexports.default = IP;\n//# sourceMappingURL=ip.js.map\n\n//# sourceURL=webpack:///./node_modules/tcomb-additional-types/lib/string/ip.js?");
The important part to note is the stuff such as const validator =. This isn't ES5.1 syntax. My own code seems to have been transpiled to ES5 just fine. I can see this file in /node_modules/tcomb-additional-types/lib/string/ip.js, where they use const, so this isn't Babel adding consts, but the source containing them. Most of the other packages are ES5.
So far, I have found that most consts are from material-ui and tcomb-additional-types.
Babel .babelrc:
{
"compact": false,
"presets": [
"es2015",
"es2017"
],
"plugins": [
["transform-runtime", {
"polyfill": false,
"regenerator": true
}],
"transform-class-properties",
"transform-react-jsx",
"transform-object-rest-spread"
]
}
Webpack config:
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
/** #returns {String} an absolute path */
function toRoot(rootRelativeDir) {
return path.resolve(__dirname, '..', rootRelativeDir);
}
module.exports = {
entry: ['./src/app.js', './styles/flex.less'].map(toRoot),
output: {
filename: 'bundle.js',
path: toRoot('.webpack/dist')
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {}
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
/* General options are read using .babelrc - only webpack loader specific here */
cacheDirectory: toRoot('.webpack/babel_cache')
}
}
]
}
]
},
plugins: [new CopyWebpackPlugin([toRoot('public')])]
};

My underlying problem was that some Node packages are not written using ES5 syntax, and the Babel transforms did not transform them for some reason. This is a normal issue
Finding why this happened was pretty easy (#Vincent's answer helped); I had exclude: /node_modules/ in the config. Of course, removing this would "fix" the issue, but it would introduce new issues, as the exclude is there for a reason, as you don't want Babel to process every file in there.
So what you want is this: selective filtering allowing some modules.
Trying to construct a regex that will allow a list of packages under node_modules, but restrict the rest is cumbersome and error prone. Thankfully the Webpack docs describe that the condition rules, of which exclude is one, can be
A string: To match the input must start with the provided string. I. e. an absolute directory path, or absolute path to the file.
A RegExp: It's tested with the input.
A function: It's called with the input and must return a truthy value to match.
An array of Conditions: At least one of the Conditions must match.
An object: All properties must match. Each property has a defined behavior.
Creating such a function is easy! So instead of having exclude: /node_modules, I changed it to be exclude: excludeCondition, where excludeCondition is the following function:
function excludeCondition(path){
const nonEs5SyntaxPackages = [
'material-ui',
'tcomb-additional-types'
]
// DO transpile these packages
if (nonEs5SyntaxPackages.some( pkg => path.match(pkg))) {
return false;
}
// Ignore all other modules that are in node_modules
if (path.match(toRoot("node_modules"))) { return true; }
else return false;
}
This fixed my issue, as there is just a tiny number of packages using ES2015 syntax, so adding them to the whitelist is manageable.
Addendum
Since people ask about the toRoot(), this is the verbatim code:
/** #returns {String} an absolute path */
function toRoot(rootRelativeDir) {
return path.resolve(__dirname, '..', rootRelativeDir);
}
Adapt to your own needs.
The fuller code:
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
/** #returns {String} an absolute path */
function toRoot(rootRelativeDir) {
return path.resolve(__dirname, '..', rootRelativeDir);
}
function excludeCondition(path) {
const nonEs2015Packages = ['tcomb-additional-types', 'material-ui'];
// DO transpile these packages
if (nonEs2015Packages.some(pkg => path.match(pkg))) {
return false;
}
// Ignore all other modules that are in node_modules
return Boolean(path.match(toRoot('node_modules')));
}
module.exports = {
entry: ['./src/app.js', './styles/custom.less', './styles/flex.less', './styles/rc_slider.less'].map(toRoot),
output: {
filename: 'bundle.js',
path: toRoot('.webpack/dist')
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {}
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: excludeCondition,
use: [
{
loader: 'babel-loader',
options: {
/* General options are read using .babelrc - only webpack loader specific here */
cacheDirectory: toRoot('.webpack/babel_cache')
}
}
]
}
]
},
plugins: [new CopyWebpackPlugin([toRoot('public')])]
};

I had a similar problem and I fixed it by renaming .babelrc.js to babel.config.js.
Apparently, .babelrc has smaller scope than babel.config.js, if you'd like to read more about that, check out this post:
When to use babel.config.js and .babelrc

The same problem happened to me as well. Some node modules don't provide browser support and target node versions that leverage newer ES syntax.
I came across that handy package that transpiles node modules code:
https://www.npmjs.com/package/babel-engine-plugin
It solved my problem regarding IE11 support, hope it helps

Related

How can I pull out certain items from rxjs?

I'm writing a lib that has rxjs as a dependency. It only uses Subject - Is it possible for me to extract that one feature and include it within my lib, removing the need for rxjs as a dependency?
No. check inner dependencies Subject rely on (https://github.com/ReactiveX/rxjs/blob/master/src/internal/Subject.ts#L1-L8). It is pretty much requiring most of primitives in rx.
Put aside of availability, if you're depends on rxjs, what reason you'd like to not to specify it as dependency?
Looks like using Webpack's tree shaking feature did it for me.
https://webpack.js.org/guides/tree-shaking/
My Webpack configuration:
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry: ['./src/index.js'],
output: {
filename: './dist/dist.bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
query: {
presets: ['env'],
plugins: ["transform-object-rest-spread"]
}
}
]
},
plugins: [
new UglifyJSPlugin()
]
}
And specified the location of the import like:
import { Subject } from "rxjs/subject"
Rather than
import { Subject } from "rxjs"
Bundle size went from 213kb to 14kb

Cannot export API in WebPack

I am creating a library in Javascript and I am shipping it as a bundle .js file using Webpack. The following file lib.js serves as the entry for Webpack in order to expose all the API in the library:
import * as bodies from "./bodies.js";
import * as composites from "./composites.js";
import * as connections from "./connections.js";
export var bodies = {
Body: bodies.Body,
Pyramid: composites.Pyramid
};
export var connections = {
Connection: connections.Connections
};
All the files imported basically export classes that I am referencing in lib.js:
// In bodies.js
export class Body { ... };
// In composites.js
export class Pyramid { ... };
// In connections.js
export class Connection { ... };
The file for bundling using Webpack is:
const path = require('path');
module.exports = {
entry: './lib.js',
output: {
filename: 'lib-bundle.js',
path: path.resolve(__dirname, 'out')
},
module: {
rules: [
/* In order to transpile ES6 */
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: { presets: ['env'] }
}
}
],
}
};
Webpack successfully bundle everything and I get my lib file in the end.
Problems using it
Then I use it in another project:
import * as mylib from "./lib/lib-bundle.js";
// Trying to use Pyramid
var pyramid = new mylib.bodies.Pyramid();
I use again Webpack to bundle this file into a file called start.js which i import in my page:
<script type="application/javascript" src="./start.js"></script>
However when running this page, I get an error. If I run the F12 tools and break in the bundle where I try creating an instance of the pyramid, there i can clearly see that object mylib does not have anything I have exposed. It is empty, lacking all the objects I exposed before.
What am I doing wrong?
You need to specify a libraryTarget in the output section of your webpack config file.
With it the bundle will correctly export your defined values, which
can be then imported with the various module loaders.
I suggest using libraryTarget: "umd" since it will add support for the most commonly used loaders. From the webpack docs:
This exposes your library under all the module definitions, allowing it to work with CommonJS, AMD and as global variable.
The resulting webpack config file is as follows:
const path = require('path');
module.exports = {
entry: './lib.js',
output: {
filename: 'lib-bundle.js',
path: path.resolve(__dirname, 'out'),
libraryTarget: 'umd',
},
module: {
rules: [
/* In order to transpile ES6 */
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: { presets: ['env'] }
}
}
],
}
};

Typescript: How to have some imports in the global scope?

Context:
I work on a project where the senior programmer decided to reduce the boilerplate code in newly created typescript files. Two examples of this boilerplate code would be importing the React library or the function that fetches and processes our localized strings.
Question:
Is it possible to have imports always available in files placed in certain folders without having to write the import tags every time?
What I've tried:
I've searched and read on the subject and found those links that talk about defining variables to use in the global space:
global.d.ts, global-modifying-module.d.ts, A typescript issue that seems to get it working
However, I was still unable to get it to work. Here is what I've tried:
At the root of the folder where I want React to be always available, I created a global.d.ts file which contains:
import * as R from "react";
declare global{
const React: typeof R;
}
With this file, the resource "React" is supposed to always be available to other files in subsequent folders. My IDE (Webstorm) recognizes that the import is there and allows me to manipulate the variable React without complaining. However, when I try to run the app, I get this error:
ReferenceError: React is not defined
I don't understand what is wrong with the code! Here is an example of the file I'm trying to render:
export default class World extends React.Component<{}, any> {
public render() {
return (<div>Hello world</div>);
}
}
From this stackoverflow question, I was under the impression that the problem could be webpack related. For the sake of completeness, here is the webpack config file we're currently using:
const webpack = require('webpack');
const path = require('path');
const BUILD_DIR = path.resolve(__dirname, './../bundles');
const WEBPACK_ENTRYFILE = path.resolve(__dirname, './../srcReact/ReactWrapper.tsx');
// `CheckerPlugin` is optional. Use it if you want async error reporting.
// We need this plugin to detect a `--watch` mode. It may be removed later
// after https://github.com/webpack/webpack/issues/3460 will be resolved.
const { CheckerPlugin } = require('awesome-typescript-loader');
const config = {
entry: [WEBPACK_ENTRYFILE],
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.less']
},
output: {
path: BUILD_DIR,
filename: 'bundle.js'
},
plugins: [
new CheckerPlugin()
],
devtool: 'source-map', // Source maps support ('inline-source-map' also works)
module: {
loaders: [
{
loader: 'url-loader',
exclude: [
/\.html$/,
/\.(js|jsx)$/,
/\.(ts|tsx)$/,
/\.css$/,
/\.less$/,
/\.ttf/,
/\.woff/,
/\.woff2/,
/\.json$/,
/\.svg$/
],
query: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
{
loader: 'url-loader',
test: /\.(ttf|woff|woff2)$/
},
{
loader: "style-loader!css-loader!less-loader",
test: /\.less$/
},
{
loader: "style-loader!css-loader",
test: /\.css$/
},
{
loader: "svg-loader",
test: /\.svg$/
},
{
loader: "json-loader",
test: /\.json$/
},
{
loader: "awesome-typescript-loader",
test: /\.(ts|tsx)$/
}
]
}
};
module.exports = config;
I am certain I am missing something. Can anyone help me?
Surely already open followed a tutorial like this
To do this creates a vendor file where you import these types of "global".
./src/vendors.ts;
import "react";
Add this file a to first place at entry parameter:
entry: { 'vendors': './src/vendors.ts', 'main': './src/main.ts' }
And add CommonChunkPlugins:
plugins: [ new CommonsChunkPlugin({
name: 'vendors'
}),
Like this in AngularClass with polyfills.

webpack - compile every scss to css file with same name

I'd like to have structure like this
-Styles
--Main.scss
--SomeComponent.scss
-CompiledStyles
--Main.css
--SomeComponent.css
Actually I can only do this
-Styles
--Main.scss
--SomeComponent.scss
--All.scss (import all scss from file)
-CompiledStyles
--Main.css ( all css)
This is my webpack config
var Path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractCSS2 = new ExtractTextPlugin('[name].css');
module.exports = {
devtool: 'eval',
entry: './Client/Styles/All.scss',
output: {
path: Path.join(__dirname, 'CompiledStyles'),
filename: 'page.js',
publicPath: '/CompiledStyles/'
},
module: {
loaders: [
{
test: /\.scss$/,
loader: extractCSS2.extract("style-loader", "css-loader!autoprefixer-loader!sass-loader")
},
{
//IMAGE LOADER
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'file-loader'
},
{
test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader?name=fonts/[name].[ext]'
}
]
},
plugins: [
extractCSS2
]
};
Is it possible to compile this scss files to single css files ?
I really don't know how to manage this case. I've tried to assign entry: './Client/Styles' but it occures error.
EDIT:
I solved this with gulp.
The idea of webpack is to put everything that is needed in some JavaScript-files. So it's the intention to not build a css-file for every css-file.
If you want to still use webpack, try this in your webpack config:
module.exports = {
// ...
entry: {
'Main': './Client/Styles/Main.scss',
'SomeComponents': './Client/Styles/SomeComponents.scss',
},
// ...
}
I have updated the answer after adamo94 noted that he used gulp, so just for everybody else some more information. To convert scss files you need a sass/scss-processor. You can easily call that processor with a single call but as you usually do more with your sources it's likely to use some further processing.
Usually you would use gulp or grunt. Those can be configured to build everything that you need. They have different pros and cons, there are also further tools, but those are probably the ones that you'd like to take a look.

Use CSS Modules in React components with Typescript built by webpack

I want to use the css-loader with the 'modules' option of webpack in a React application written in Typescript. This example was my starting point (they are using Babel, webpack and React).
webpack config
var webpack=require('webpack');
var path=require('path');
var ExtractTextPlugin=require("extract-text-webpack-plugin");
module.exports={
entry: ['./src/main.tsx'],
output: {
path: path.resolve(__dirname, "target"),
publicPath: "/assets/",
filename: 'bundle.js'
},
debug: true,
devtool: 'eval-source-map',
plugins: [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({minimize: true})
],
resolve: {
extensions: ['', '.jsx', '.ts', '.js', '.tsx', '.css', '.less']
},
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts-loader'
},
{
test: /\.tsx$/,
loader: 'react-hot!ts-loader'
}, {
test: /\.jsx$/,
exclude: /(node_modules|bower_components)/,
loader: "react-hot!babel-loader"
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: "babel-loader"
}, {
test: /\.css/,
exclude: /(node_modules|bower_components)/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader')
}
]
},
plugins: [
new ExtractTextPlugin("styles.css", {allChunks: true})
],
postcss: function() {
return [require("postcss-cssnext")()]
}
}
This is a React component I want to style with an accompanying CSS file:
import React = require('react');
import styles = require('../../../css/tree.css')
class Tree extends React.Component<{}, TreeState> {
...
render() {
var components = this.state.components
return (
<div>
<h3 className={styles.h3} >Components</h3>
<div id="tree" className="list-group">
...
</div>
</div>
)
}
}
export = Tree
tree.css
.h3{
color: red;
}
No matter what I'm doing (tried changing the import syntax, tried declaring the 'require' for ts-loader, described here, I always get:
Uncaught Error: Cannot find module "../../../css/tree.css"
at runtime and
error TS2307: Cannot find module '../../../css/tree.css'.
by the TS compiler. Whats happening? Seems to me that css-loader is not even emitting ICSS? Or is it ts-loader behaving wrong?
import has special meaning to TypeScript. It means that TypeScript will attempt to load and understand the thing being imported. The right way is to define require like you mentioned but then var instead of import:
var styles = require('../../../css/tree.css')`
Declare 'require' as per ts-loader documentation.
Use 'require' as generic with < any > type: require< any >("../../../css/tree.css").
*.d.ts file
declare var require: {
<T>(path: string): T;
(paths: string[], callback: (...modules: any[]) => void): void;
ensure: (paths: string[], callback: (require: <T>(path: string) => T) => void) => void;
};
*.tsx file with component
const styles = require<any>("../../../css/tree.css");
...
<h3 className={styles.h3}>Components</h3>
I know it was already answered, but I was struggling with it for a while before I realized I need to use generic type specification, without that I wasn't able to access content of CSS file. (I was getting error: Property 'h3' does not exists on type '{}'.)
I had similar problem.
For me, works import:
import '../../../css/tree.css';
Webpack change this like any other normal imports. It change it to
__webpack_require__(id)
One drawback is that you lost control on style variable.
You can use https://github.com/Quramy/typed-css-modules, which creates .d.ts files from CSS Modules .css files. Please see also https://github.com/css-modules/css-modules/issues/61#issuecomment-220684795
A bit late to game but you can create a file called tree.css.d.ts in the same folder as tree.css that has this line:
export const h3: string;
and still use the import statement import * as styles from ... and you will still getcode completion and compile time checking.
You can either manage these definition files manually or you could integrate typed-css-modules into your build pipeline (https://github.com/Quramy/typed-css-modules)

Categories