I have a Webpack build for a universal Javascript application. I am using the DLL plugin to pre-build all my node_modules. I added a lib that is causing the DLL build to error out (see below).
I can probably add a JSON loader to solve the problem. But I don't want the lib in the React code at all. I added it to my exclusion list, but it is still throwing an error.
Here's the error:
Building the Webpack DLL...
Hash: a69a927bfa72ddef88d5
Version: webpack 2.1.0-beta.15
Time: 7152ms
Asset Size Chunks Chunk Names
reactBoilerplateDeps.dll.js 5.58 MB 0 [emitted] reactBoilerplateDeps
chunk {0} reactBoilerplateDeps.dll.js (reactBoilerplateDeps) 5.07 MB [rendered]
[1135] dll reactBoilerplateDeps 12 bytes {0} [built]
+ 1137 hidden modules
ERROR in ./~/constants-browserify/constants.json
Module parse failed: /Users/steve/Projects/elucidate/node_modules/constants-browserify/constants.json Unexpected token (2:12)
You may need an appropriate loader to handle this file type.
| {
| "O_RDONLY": 0,
| "O_WRONLY": 1,
| "O_RDWR": 2,
# ./~/graceful-fs/polyfills.js 2:16-36
Webpack DLL build script:
const { join } = require('path');
const defaults = require('lodash/defaultsDeep');
const webpack = require('webpack');
const pkg = require(join(process.cwd(), 'package.json'));
const dllPlugin = require('../config').dllPlugin;
if (!pkg.dllPlugin) { process.exit(0); }
const dllConfig = defaults(pkg.dllPlugin, dllPlugin.defaults);
const outputPath = join(process.cwd(), dllConfig.path);
module.exports = {
context: process.cwd(),
entry: dllConfig.dlls ? dllConfig.dlls : dllPlugin.entry(pkg),
devtool: 'eval',
output: {
filename: '[name].dll.js',
path: outputPath,
library: '[name]',
},
node: {
fs: "empty",
},
plugins: [
new webpack.DllPlugin({ name: '[name]', path: join(outputPath, '[name].json') }), // eslint-disable-line no-new
],
};
DLL Plugin configuration from package.json:
"dllPlugin": {
"path": "node_modules/react-boilerplate-dlls",
"exclude": [
"chalk",
"compression",
"cross-env",
"express",
"ip",
"minimist",
"sanitize.css",
"multiparty",
"cloudinary",
"winston",
"morgan",
"body-parser",
"request-promise",
"winston-graylog2",
"yauzl",
"busboy",
"graceful-fs"
],
"include": [
"core-js",
"lodash",
"eventsource-polyfill"
]
},
I think you can exclude some modules with IgnorePlugin or ignore-loader.
https://webpack.github.io/docs/list-of-plugins.html#ignoreplugin
https://github.com/cherrry/ignore-loader
NOTE: This is for react-boilerplate only.
To exclude the module from the DllPlugin you need to add the module (or the module that has it marked at its dependant in your excludes array):
excludes = {
"constants-browserify",
... }
If is important to note that if you did not install the constants-browserify module directly, you will need to find the module that has marked it as a dependancy.
Alternatively, and as you say, if you want load the module then you need to specify a loader for the .json file that the DllPlugin is attempting to parse:
Place:
module: {
loaders: [
{
test: /\.json$/,
loader: 'json-loader',
}
],
},
inside of your
module.exports = { ... }
This will allow WebPack to correctly parse the .json file.
Related
When I try to use the loader for mini-css-extract-plugin webpack returns the following error:
/node_modules/mini-css-extract-plugin/dist/loader.js:122
for (const asset of compilation.getAssets()) {
^
TypeError: compilation.getAssets(...) is not a function or its return value is not iterable
I am requiring the plugin and invoking the loader in my prod config file:
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const path = require("path");
const common = require("./webpack.common");
const merge = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = merge(common, {
mode: "production",
output: {
filename: "[name].[contentHash].bundle.js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new MiniCssExtractPlugin({filename: "[name].[contentHash].css"}),
new CleanWebpackPlugin()
],
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader, //3. Extract css into files
"css-loader", //2. Turns css into commonjs
"sass-loader" //1. Turns sass into css
],
},
],
},
});
I have it included in my devDependencies:
"mini-css-extract-plugin": "^1.3.6",
But still I receive the error. I haven't found anything in the [documentation][1] to indicate what could be happening. Is there something I'm overlooking with this code?
Why would methods from loader.js be getting flagged as 'not a function?'
[1]: https://www.npmjs.com/package/mini-css-extract-plugin
I understand getAssets is only available since webpack 4.40.0 https://webpack.js.org/api/compilation-object/#getassets
You could try:
Update webpack version to at least 4.40.0
Downgrade min-css-extract-plugin to 1.3.0
I tried the second one, and worked for me, but upgrading webpack should work too.
I'm struggling with what looks like a generic error from Webpack after trying to optimise my source code.
Assuming I have the following files in ./src:
├── main.js
├── moduleA.js
└── moduleB.js
main.js imports and uses ModuleA.
moduleA.js imports and uses ModuleB
ModuleA.js and ModuleB.js both import flatten-array from node_modules
My expectation is that if I try to optimise my bundle (see below) it will output two files:
1. index.js
2. vendors~main.index.js
Trying to execute the index.js output bundle results in:
/******/ modules[moduleId].call(module.exports, module,
module.exports, __webpack_require__);
^
TypeError: Cannot read property 'call' of undefined
Although the files are generated, index.js doesn't appear to import vendors~main.index.js. Yet it executes fine when removing the optimization (and vendors javascript)
Is this the correct assumption? How can I make it work like this?
While this is a bundle for Node, there are valid reasons that I'd like to export a vendors file.
Accompanying git repo to reproduce available here:
https://github.com/supasympa/webpack-vendors-issue
Files are:
main.js
const moduleA = require('./moduleA');
moduleA.log('log from main.js');
moduleA.js
const moduleB = require('./moduleB');
const flatten = require('array-flatten');
module.exports.log = function(msg){
moduleB.log('logging from moduleA.js');
console.log(`ModuleA logging: ${msg}`);
console.log(`flattened: ${flatten([[1,2,3],[4,5],[6,7]])}`)
};
moduleB.js
const flatten = require('array-flatten');
module.exports.log = function(msg){
console.log(`ModuleB logging: ${msg}`);
console.log(`flattened: ${flatten([[1,2,3],[4,5],[6,7]])}`)
};
webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
module: {
rules: [{
include: [path.resolve(__dirname, 'src')],
loader: 'babel-loader',
options: {
plugins: ['syntax-dynamic-import'],
presets: [['env', {
'modules': 'commonjs'
}]]
},
test: /\.js$/
}]
},
entry: './src/main',
target: 'node',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
priority: -10,
test: /[\\/]node_modules[\\/]/,
enforce: true
},
},
// concatenateModules: false,
chunks: 'all',
minChunks: 1,
minSize: 0,
name: true
}
},
plugins: [
new CleanWebpackPlugin(['dist']),
]
};
It turns out that this is not yet implemented outside of the browser, in Webpack.
https://github.com/webpack/webpack/issues/8330
https://github.com/webpack/webpack/issues/8161
https://github.com/webpack/webpack/issues/8156
Defining, chunks: 'all', you're explicitly taking all initial entries and async imports (by default it only on-demand async chunks) and specifying the bundler to create a new vendor chunk/file out of that.
So the behavior is as expected. The idea is to remove the common dependencies from entry files so they can be shared and the page has to load less vendors/common code overall.
One way to explicitly control what the entry files include is this pattern: https://github.com/webpack/webpack/tree/master/examples/two-explicit-vendor-chunks
I am using Django with Webpack and Vue, And Code splitting in Webpack. Webpack split chunk files from source code of .vue files. But, I can't load chunk files on Web browser because url is incorrect in my project structure. I want to change load url of chunk files but I don't know these. How to change load url of chunk files?
Chrome console logs:
GET http://localhost:8123/mycomponent.chunk.js 404 (Not Found)
main.js:12 Error: Loading chunk 2 failed.
(error: http://localhost:8123/mycomponent.chunk.js)
at HTMLScriptElement.a (main.js:1)
I need url of '/static/js/mycomponent.chunk.js'.
webpack.config.js:
const path = require("path")
const webpack = require('webpack')
const BundleTracker = require('webpack-bundle-tracker')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
context: __dirname,
entry: './src/main.js',
output: {
path: path.resolve('../static/js/'),
filename: "[name].js",
chunkFilename: '[name].chunk.js',
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
],
},
plugins: [
new BundleTracker({filename: './webpack-stats.json'}),
new VueLoaderPlugin(),
],
resolve: {
alias: {
'vue': path.resolve('./node_modules/vue/dist/vue.js'),
}
},
}
Try adding publicPath: '/static/js/' to output.
Also, if you serve your js from your Django template page, you can use django-webpack-loader, it simplifies serving js files and supports hot module reloading.
I want to set up an Angular 1.x app from scratch using webpack 2.
I am having trouble finding the best configuration for webpack.config, with optimal entry and output for production (meaning, all code, style and templating minified and gziped with no code repetition).
My main problem is how to set up webpack.config so that it recognizes all partials within the folder structure of my project, like these:
My current config file, for reference (which can't see subfolders):
var HtmlWebpackPlugin = require( 'html-webpack-plugin' );
var ExtractTextPlugin = require( 'extract-text-webpack-plugin' );
var path = require( 'path' );
module.exports = {
devServer: {
compress: true,
contentBase: path.join( __dirname, '/dist' ),
open: true,
port: 9000,
stats: 'errors-only'
},
entry: './src/app.js',
output: {
path: path.join( __dirname, '/dist' ),
filename: 'app.bundle.js'
},
module: {
rules: [ {
test: /\.scss$/,
use: ExtractTextPlugin.extract( {
fallback: 'style-loader',
use: [
'css-loader',
'sass-loader'
],
publicPath: '/dist'
} )
} ]
},
plugins: [
new HtmlWebpackPlugin( {
hash: true,
minify: { collapseWhitespace: true },
template: './src/index.html',
title: 'Prov'
} ),
new ExtractTextPlugin( {
filename: 'main.css',
allChunks: true
} )
]
};
Note that this isn't an exhaustive solution, as there are many optimizations one can make in the frontend, and I've kept the code snippets fairly short.
With webpack, there are a few routes that you can take to include partials into your app.js.
Solution 1
You can import/require your partials within app.js as such:
app.js
var angular = require('angular');
var proverbList = require('./proverb/list/proverb.list');
// require other components
// set up your app as normal
This allows the app.bundle.js to include your component js files in the main bundle. You can also use html-loader to include templates in the final bundle.
This isn't ideal, as all it does is create a large bundle.js (which doesn't leverage multiple downloads with http2 nor does it allow loading of components/files when the user explicitly requires it).
Solution 2
Importing partials as separate entry files into your webpack bundle:
webpack.config.js
const globby = require('globby');
const sourceDir = 'src';
var webpackentry = {
app: `${__dirname}/src/app.js`
};
const glob = globby.sync(`${__dirname}/${sourceDir}/**/*.js`)
.map((file)=>{
let name = file.split('/').pop().replace('.js', '');
webpackentry[name] = file;
});
const config = {
entry: webpackentry,
...
}
The second solution is unorthodox but it can be useful if you wanted to split all your partials as <script> tags in your html (for example if your company/team uses that as a means to include your directive/components/controllers), or if you have an app-2.bundle.js.
Solution 3
Use CommonsChunkPlugin:
webpack.config.js
let webpackentry = {
vendor: [
'module1',
'module2',
'module3',
]
}
...
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor'] //... add other modules
})
]
CommonsChunkPlugin allows webpack to scrawl through your entry files and discern common modules that are shared among them. This means that even if you are importing module1 in different files, they will be compiled only once in your final bundle.
In isomorphic react app I have myModule which should behave differently on node and browser environments. I would like configure this split point in package.json for myModule:
package.json
{
"private": true,
"name": "myModule",
"main": "./myModule.server.js",
"browser": "./myModule.client.js"
}
file structure
├── myModule
│ ├── myModule.client.js
│ ├── myModule.server.js
│ └── package.json
│
├── browser.js
└── server.js
So when I use myModule in node I should get only myModule.server.js:
server.js
import myModule from './myModule';
myModule(); // invoke myModule.server.js
On the browser side should build bundle only with myModule.client.js:
browser.js
import myModule from './myModule';
myModule(); // invoke myModule.client.js
react-starter-kit uses this approach but I can't figure out where is this configuration defined.
Motivation
package.json is good semantic point to do this kind of splitting.
Client side bundle only contain myModule.client.js.
Known solution - not an answer for me
You can have this kind of file structure:
├── myModule
│ ├── myModule.client.js
│ ├── myModule.server.js
│ └── index.js <-- difference
│
├── browser.js
└── server.js
And in index.js:
if (process.browser) { // this condition can be different but you get the point
module.exports = require('./myModule.client');
} else {
module.exports = require('./myModule.server');
}
The main problem with this is that client bundle contains a lot of heavy kB backend code.
My webpack configuration
I include my webpack.config.js. Strangely this config always point to myModule.client.js for browser and node.
const webpack = require('webpack');
var path = require('path');
var fs = require('fs');
const DEBUG = !process.argv.includes('--release');
const VERBOSE = !process.argv.includes('--verbose');
const AUTOPREFIXER_BROWSERS = [
'Android 2.3',
'Android >= 4',
'Chrome >= 35',
'Firefox >= 31',
'Explorer >= 9',
'iOS >= 7',
'Opera >= 12',
'Safari >= 7.1',
];
let nodeModules = {};
fs.readdirSync('node_modules')
.filter(function(x) {
return ['.bin'].indexOf(x) === -1 ;
})
.forEach(function(mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
let loaders = [
{
exclude: /node_modules/,
loader: 'babel'
},
{
test: [/\.scss$/,/\.css$/],
loaders: [
'isomorphic-style-loader',
`css-loader?${DEBUG ? 'sourceMap&' : 'minimize&'}modules&localIdentName=` +
`${DEBUG ? '[name]_[local]_[hash:base64:3]' : '[hash:base64:4]'}`,
'postcss-loader?parser=postcss-scss'
]
},
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/,
loader: 'url-loader',
query: {
name: DEBUG ? '[name].[ext]' : '[hash].[ext]',
limit: 10000,
},
},
{
test: /\.(eot|ttf|wav|mp3)$/,
loader: 'file-loader',
query: {
name: DEBUG ? '[name].[ext]' : '[hash].[ext]',
},
},
{
test: /\.json$/,
loader: 'json-loader',
},
];
const common = {
module: {
loaders
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
],
postcss: function plugins(bundler) {
var plugins = [
require('postcss-import')({ addDependencyTo: bundler }),
require('precss')(),
require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }),
];
return plugins;
},
resolve: {
root: path.resolve(__dirname, 'src'),
extensions: ['', '.js', '.jsx', '.json']
}
};
module.exports = [
Object.assign({} , common, { // client
entry: [
'babel-polyfill',
'./src/client.js'
],
output: {
path: __dirname + '/public/',
filename: 'bundle.js'
},
target: 'web',
node: {
fs: 'empty',
},
devtool: DEBUG ? 'cheap-module-eval-source-map' : false,
plugins: [
...common.plugins,
new webpack.DefinePlugin({'process.env.BROWSER': true }),
],
}),
Object.assign({} , common, { // server
entry: [
'babel-polyfill',
'./src/server.js'
],
output: {
path: __dirname + '',
filename: 'server.js'
},
target: 'node',
plugins: [
...common.plugins,
new webpack.DefinePlugin({'process.env.BROWSER': false }),
],
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
},
externals: nodeModules,
})
];
The behavior is standardized here: https://github.com/defunctzombie/package-browser-field-spec
Although this specification is unofficial, many Javascript bundlers follow it, including Webpack, Browserify, and the React Native packager. The browser field not only allows you to change your module entry point, but to also replace or ignore individual files within your module. It's quite powerful.
Since Webpack bundles code for the web by default, you need to manually disable the browser field if you want to use Webpack for your server build. You can do that using the target config option to do this: https://webpack.js.org/concepts/targets/
It has been a long time since this question was asked. I just want to clarify the previous answer.
If you look at tools/webpack.config.js in React Starter Kit you will
see that it exports two Webpack configurations that slightly differ,
e.g. module.exports = [clientConfig, sererConfig]. The server-side
bundle config has this field target set to node (by default it's web).
It seems this webpack beheavior is not documented, but webpack automatically takes 'main' entry when target is 'node' and takes 'browser' entry when target is 'web'.
If you look at tools/webpack.config.js in React Starter Kit you will see that it exports two Webpack configurations that slightly differ, e.g. module.exports = [clientConfig, sererConfig]. The server-side bundle config has this field target set to node (by default it's web).
https://webpack.github.io/docs/configuration.html#target
The approach that you described works great for modules that have exactly the same API but different implementations, like in the case with HTTP client utility that uses XMLHttpRequest in its browser-specific implementation and Node's http module in its server implementation:
https://github.com/kriasoft/react-starter-kit/tree/master/src/core/fetch
To have a different entry point for client and server in a Node Module, you can use process.browser flag and handle the same
if (process.browser) {
// load client entry point
} else {
// load server entry point
}