Webpack externals should not be imported - javascript

My app contains different modules, moduleA, moduleB and moduleC. These will be included as dependencies in package.json of my App.
All of these components contain some common scripts, react, react-dom, lodash, etc.
What is the webpack config to build these modules SEPARATELY and then require them in my app? I'm very confused with webpack doc.
Attempt 1: using externals
I defined a common module with index.js containg:
require('react');
require('react-dom');
require('redux');
require('react-redux');
require('moment');
require('lodash');
Then the config file exposed these:
module: {
loaders: [{
test: require.resolve("react"),
loader: "expose?React"
},
...
This common.js is then included before any other file. Then inside each module, using externals I have:
externals: {
"react": "React",
"react-dom": "ReactDom",
...
},
Problem
If I use import React from 'react' in any of my modules, a new instance of react is imported, causing invariance problem and eventual addComponentAsRefTo error.
However, I can perfectly use React as it is globally available.
Attempt 2: CommonsChunkPlugin
Still include common.js from the top. But this time for each module, use vendors:
entry: {
"app": "./src/myentry.jsx",
"vendor": ["react", "react-dom"]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
]
Problem
Each module creates its own vendor file. How do I then combine all vendor files?

I think there are two approaches:
1) load react/react-dom from a CDN, and your bundle separately:
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<script src="app.js"></script>
This way, you should not import react (it's already loaded and exposed globally). ReactDOM.render, for instance, should work just fine. If you import, you are going to load into your bundle another instance of React (from ./node_modules), which will throw an error and increase your bundle size.
2) the second option (which I use) is to configure webpack to create a common/vendor bundle:
entry: {
"app: "./src/myentry.jsx",
"vendor": ["react", "react-dom"]
},
output: {
filename: "[name].js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
]
Webpack will correctly separate the codes and generate both app.js and vendor.js. Then you should just load them on your page:
<script src="vendor.js"></script>
<script src="app.js"></script>

if you want to use a remote or cdn react react-dom etc files, why you require them in your common.js. as your know CMD, if your require in your common.js file
require('react');
require('react-dom');
require('redux');
require('react-redux');
require('moment');
require('lodash');
then the variable React, ReactDom is not a golbal variable. your cann't visit it in your other files.
you can do it like this:
window.React = require('react');
window.ReactDom = require('react-dom');
window.Redux = require('redux');
window.ReactRedux = require('react-redux');
window.monment = require('moment');
window.lodash = require('lodash');
and insert such a srcript tag into your html <srcipt src="/common.bunld.js">
just make sure common.js is in your webpack entry.

Related

How to exclude CommonJS libs from production build with Webpack (vue-cli)

I have managed to do that for vue by using Webpack config externals
First I included the CDN for Vue in my html file
index.html
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
Then I modified my webpack config
vue.config.js
module.exports = {
configureWebpack: {
// ...
externals: {
vue: 'Vue',
}
// ...
},
}
Things worked perfectly.
But when I tried with vue-class-component and vue-property-decorator, it didn't not worked as expected
index.html
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-class-component#7.2.5"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-property-decorator#9.0.0"></script>
vue.config.js
module.exports = {
configureWebpack: {
// ...
externals: {
vue: 'Vue',
'vue-class-component': 'VueClassComponent',
'vue-property-decorator': 'VuePropertyDecorator',
}
// ...
},
}
I noticed that the names of these files are different, end with .common.js and .umd.js
vue-class-component.common.js
vue-property-decorator.umd.js
Then I tried
vue.config.js
module.exports = {
configureWebpack: {
// ...
externals: {
vue: 'Vue',
'vue-class-component': 'commonjs2 vue-class-component',
'vue-property-decorator': 'umd vue-property-decorator',
}
// ...
},
}
But it did not work as well
Below are how I import these in my src/. Scripts are written in typescript
import Vue from 'vue'
// ...
import { Component } from 'vue-property-decorator'
Anyone knows how to handle externals in webpack with .common.js and .umd.js? Many thanks!
I don't think problem is necessarily in Webpack config...
If I try to load https://cdn.jsdelivr.net/npm/vue-class-component#7.2.5 it gives me Original file: /npm/vue-class-component#7.2.5/dist/vue-class-component.common.js which is CommonJS build - browser will not handle that. Try use link to a "browser compatible" build like https://cdn.jsdelivr.net/npm/vue-class-component#7.2.6/dist/vue-class-component.min.js
vue-property-decorator should be fine as UMD module should work in the browser...
BTW whats the point of all this? Why not let Webpack do its thing ? Its always better do download one big JS file then multiple smaller...

extracting libraries to separate file

I'm trying to extract libraries in a separate vendor.js file.
In webpack.config.js (webpack 4), I do this :
entry: {
app: './resources/assets/js/app.js',
vendor: ['vue', 'axios']
},
output: {
path: path.resolve(__dirname, 'public/js'),
filename: '[name].js',
publicPath: './public'
}
In the end, I have my app.js and vendor.js
The vendor.js file includes both vue and axios.
In my html file, at the end, I add the 2 scripts :
<script src="/js/app.js"></script>
<script src="/js/vendor.js"></script>
But app.js does not know about Vue or axios.
I get "ReferenceError: Vue is not define".
Same if putting vendor.js before app.js in the html file.
Should I add a line in my "app.js" entry point, so that I import vue and axios in some way, without adding the libraries in the "app.js" output ?
The Webpack 4 way of splitting out a "vendor" chunk is via the SplitChunksPlugin. You can configure this plugin through the optimization.splitChunks config path:
// webpack.config.js
module.exports = {
// ...
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](vue|axios)[\\/]/,
name: 'vendor',
chunks: 'all'
}
}
}
}
};
Perhaps counter-intuitively, the way to reference these modules after this configuration remains the same; how vue and axios are loaded is decided by the webpack config; you don't have to change any code that used to import these modules. require('vue') or import axios from 'axios' will cause these modules to be referenced from your vendor.js file.
Finally, re-order your script tags so that app.js doesn't try to load things from vendor.js before vendor.js is loaded.
<script src="/js/vendor.js"></script>
<script src="/js/app.js"></script>

Is there a way in Webpack to extract imported CSS into multiple files?

This is my folder structure:
src/yolo/block.js
src/yolo/editor.scss
src/yolo/style.scss
This is an excerpt of my webpack.config.js
module.exports = {
entry: glob.sync('src/**/block.js'),
output: { path: 'dist' },
plugins: [new MiniCssExtractPlugin()],
...
}
This is what the js file looks like:
block.js
import './editor.scss'
import './style.scss'
I expect the output to be:
dist/yolo/block.js -> es5
dist/yolo/editor.css
dist/yolo/style.css
But instead I get:
dist/yolo/block.js
dist/yolo/block.css
What Webpack does here is to compile all CSS and JS dependencies in 2 files because they are required/imported in the block.js. Importing a file means your code needs them, it would be wrong not to package them with Webpack.
If you want Webpack to compile different CSS/JS in different files you have to create another JS file that will include only one CSS file and remove the appropriate import from block.js.

Webpack and toastr

I am trying to load and bundle toastr as a dependency using webpack.
here is the entire webpack config file
var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require('webpack');
const DEVELOPMENT = process.env.NODE_ENV === 'development';
const PRODUCTION = process.env.NODE_ENV === 'production';
module.exports = {
entry: {
main: './wwwroot/js/mainEntry.js',
vendor: ['jquery', 'tether',
'bootstrap', 'jquery-colorbox',
'jquery-confirm', 'jquery-validation',
'jquery-validation-unobtrusive',
'toastr', 'jquery.nicescroll',]
},
output: {
filename: '/js/[name].js',
path: path.resolve(__dirname, 'wwwroot'),
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader?name=[name].[ext]&publicPath=/fonts/&outputPath=/fonts/'
},
{
test: /\.(png|jpe?g|gif|ico)$/,
loader: 'file-loader?name=[name].[ext]&publicPath=/images/&outputPath=/images/'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// (the commons chunk name)
filename: "/js/vendor.js",
// (the filename of the commons chunk)
minChunks: 2,
}),
new ExtractTextPlugin({
filename: 'css/[name].min.css'
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
],
};
and my entry js file as
//JS
import 'jquery-colorbox';
import 'slick-carousel';
import toastr from 'toastr';
//CSS
import './../../node_modules/bootstrap/dist/css/bootstrap.css';
import './../../node_modules/slick-carousel/slick/slick.css';
import './../../node_modules/jquery-colorbox/colorbox.css';
import './../../node_modules/toastr/build/toastr.css';
import './../../node_modules/jquery-confirm/css/jquery-confirm.css';
import './../../node_modules/font-awesome/css/font-awesome.css';
import './../../Modules/Components/Menu/menu.css';
import './../../wwwroot/css/lahuritv.css';
The bundle is created without any errors. And I can see that the toastr script is included in the bundle when I look at the output bundle. But the problem is that the variable toastr is not available in the browser window.
I tried looking for similar issue but couldn't find any. This is my first time trying to learn webpack. Any help is appreciated.
Simple solution :
//app.js
import toastr from 'toastr'
window.toastr = toastr
Webpack does not expose the modules you import, it bundles your code together with the needed dependencies, but they are still modular and have their own scope. Webpack does not just blindly combine the sources of all the files and certainly does not make everything global. If the entry file you posted is the whole file, then you're not doing anything at all.
Instead with webpack you would do the work in the JavaScript files you bundle. So to speak, all you include in your HTML are the bundles created by webpack, you don't include other scripts that try to access something in the bundle, but you rather do it directly in your bundle.
Of course you could expose it to window by explicitly defining it: window.toastr = toastr after importing toastr, but polluting the global scope is generally not a good idea and it's no different from just including toastr in a <script> tag. If the bundle is just supposed to be used as a library you could have a look at Authoring Libraries. But I think you're just doing a regular web app and you should get all the code together to be bundled by webpack and not rely on it in another <script> tag.
You should go through the Getting Started guide of the official docs. The example is very tiny (creates one DOM element) but shows you the concept of webpack apps and also uses an external dependency.

How to use scss with css modules / postcss inside components

I'm failing to figure out how to setup my webpack in order to be able and use scss modules inside my components like:
import styles from './ComponentStyles.scss'
So far I tried configuring webpack to use sass-loader alongside postcss-loader but had no luck:
{
test: /\.scss$/,
loaders: [
'isomorphic-style-loader',
'css-loader?modules&localIdentName=[name]_[local]_[hash:base64:3]',
'postcss-loader',
'scss-loader'
]
}
Note isomorphic-style-loader is a library I use instead of style-loader due to server rendering requirements, in its github page docs they actually use postcss-loader with .scss extension, but in my case scss is not compiled if I follow their example.
I had to do some tinkering myself, but eventually landed on the following
package.json
"autoprefixer": "^6.3.1",
"css-loader": "^0.23.1",
"extract-text-webpack-plugin": "^1.0.1",
"node-sass": "^3.8.0",
"sass-loader": "^3.1.2",
"style-loader": "^0.13.0"
webpack config
...
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const autoprefixer = require('autoprefixer');
...
const config = {
...
postcss: [
autoprefixer({
browsers: ['last 2 versions']
})
],
plugins: [
new ExtractTextPlugin('css/bundle.css'),
],
module: {
loaders: [
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract('style', 'css!postcss!sass')
}
]
}
...
};
bootstrap.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import style from './scss/style.scss';
ReactDOM.render(
<App/>,
document.getElementById('my-app')
);
For those interested: what's happening here is bootstrap.jsx is the webpack entry point, and by importing our raw scss file (via relative path), we tell webpack to include it during the build process.
Also, since we've specified a loader for this file extension in our configuration (.scss), webpack is able to parse style.scss and run it through the defined loaders from right to left: sass --> post-css --> css.
We then use extract-text-webpack-plugin to pull the compiled CSS out from bundle.js, where it would normally reside, and place it in a location (css/bundle.css) relative to our output directory.
Also, using extract-text-webpack-plugin is optional here, as it will simply extract your CSS from bundle.js and plop it in a separate file, which is nice if you utilize server-side rendering, but I also found it helpful during debugging, since I had a specific output location for the scss I was interested in compiling.
If you're looking to see this in action, here's a small boilerplate that uses it: https://github.com/mikechabot/react-boilerplate

Categories