webpack配置推荐 - xw332/xw332.github.io GitHub Wiki
中文网的部分配置已经失效,可以参考英文官网:
clean-webpack-plugin
webpack.optimize.CommonsChunkPlugin
tree-shaking
缓存部分
遗留问题:
- html和css在引用同一个小图时,会以重复的base64代码包含在各自的文件里(考虑这类图片单独文件夹并保持链接形式)
- html-withimg-loader不支持ejs,会导致html-webpack-plugin解析变量失败(考虑通过require方式引入图片)
- 图片和css最终输出没有压缩
- image-webpack-loader做图片压缩,会build失败,需要用cnpm的源重新安装一遍才能成功(神)。另外默认的压缩比例,居然让图片变大,放弃。
我的webpack-demo配置
// package.json
{
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config config/webpack.dev.js",
"build:dev": "webpack --config config/webpack.dev.js",
"build": "NODE_ENV=production webpack --config config/webpack.prod.js",
"lint": "eslint --ext .js --cache --fix dev config",
"precommit": "npm run lint && npm run build",
"browserslist": "browserslist"
},
"dependencies": {
"@babel/runtime": "^7.7.7",
"axios": "^0.19.0",
"mockjs": "^1.1.0",
"mockjs-fetch": "^1.0.3",
"query-string": "^6.9.0"
},
"devDependencies": {
"@babel/core": "^7.7.7",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.7",
"@babel/runtime-corejs3": "^7.7.7",
"autoprefixer": "^9.7.3",
"babel-eslint": "^11.0.0-beta.2",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.1",
"cssnano": "^4.1.10",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.9.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-prettier": "^3.1.2",
"file-loader": "^5.0.2",
"glob": "^7.1.6",
"happypack": "^5.0.1",
"html-webpack-plugin": "^4.0.0-beta.11",
"html-withimg-loader": "^0.1.16",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss-loader": "^3.0.0",
"prettier": "^1.19.1",
"style-loader": "^1.1.2",
"uglify-es": "^3.3.9",
"url-loader": "^3.0.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1",
"webpack-merge": "^4.2.2",
"workbox-webpack-plugin": "^4.3.1"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8",
"Android >= 4.0"
]
}
// webpack.common.js
const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HappyPack = require('happypack');
const loadMinified = require('./load-minified');
const autoprefixer = require('autoprefixer');
function resolve(dir) {
return path.resolve(__dirname, '..', dir);
}
const pathSrc = 'dev/public';
const pathDist = 'src/static/public';
const devMode = process.env.NODE_ENV !== 'production';
let entry = {};
let plugins = [];
function makeEntry() {
entry = glob.sync('./dev/**/js/index.js').reduce((map, relativePath) => {
const chunk = relativePath.match(/^\.\/dev\/([^/]+)/)[1];
map[chunk] = relativePath;
return map;
}, {});
plugins = [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [resolve(pathDist)],
}),
new CopyWebpackPlugin([
{ from: `${pathSrc}/favicon.ico`, to: '../html' },
]),
// new webpack.DllReferencePlugin({
// manifest: require('../dll/vendors-manifest.json'),
// }),
new MiniCssExtractPlugin({
filename: devMode ? '../style/[name].css' : '../style/[name].[chunkhash:8].css',
chunkFilename: devMode ? '../style/[name].css' : '../style/[name].[chunkhash:8].css',
}),
new HappyPack({
id: 'js',
loaders: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/transform-runtime'],
cacheDirectory: true,
cacheCompression: true,
compact: true,
},
},
],
}),
];
Object.keys(entry).forEach(function(chunk) {
plugins.push(
new HtmlWebpackPlugin({
hash: devMode, // 内嵌js和css加hash
chunks: [chunk],
filename: `../html/${chunk}.html`,
template: `html-withimg-loader?min=false!./dev/${chunk}/index.html`,
serviceWorkerLoader: `<script>${loadMinified(path.resolve(__dirname, 'service-worker-prod.js'))}</script>`,
minify: devMode
? false
: {
collapseWhitespace: true,
removeComments: true,
// removeAttributeQuotes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
minifyCSS: true,
minifyJS: true,
},
})
);
});
}
makeEntry();
module.exports = {
entry,
output: {
path: resolve(`${pathDist}/js`),
filename: '[name].[chunkhash:8].js',
chunkFilename: '../js/[name].[chunkhash:8].js',
// publicPath: '/',
},
resolve: {
extensions: ['.js', '.json'],
alias: {
'@': resolve(pathSrc),
'@style': resolve(`${pathSrc}/style`),
'@img': resolve(`${pathSrc}/img`),
},
},
plugins,
module: {
rules: [
{
test: /\.js$/,
include: resolve('dev'),
exclude: /node_modules/,
use: ['happypack/loader?id=js'],
},
{
test: /\.(css|less)$/,
include: resolve('dev'),
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: devMode,
},
},
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer()],
},
},
'less-loader',
],
},
{
test: /\.(png|gif|jpe?g|svg)$/,
include: resolve('dev'),
exclude: /node_modules/,
use: [
{
loader: 'url-loader',
options: {
name() {
// 保持和开发目录一致
if (devMode) return '[path][name].[ext]';
return '[name].[contenthash:8].[ext]';
},
outputPath: devMode
? '../img'
: (name, resourcePath, context) => {
const relativePath = path.relative(`${context}/dev`, resourcePath);
const [pathName] = relativePath.match(/^[^/]+/);
return `../img/${pathName}/${name}`;
},
// publicPath: '../img',
esModule: false,
limit: 8192,
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
include: resolve('dev'),
exclude: /node_modules/,
use: [
{
loader: 'file-loader',
options: {
name() {
if (devMode) return '[path][name].[ext]';
return '[name].[contenthash:8].[ext]';
},
outputPath: devMode
? '../icon'
: (name, resourcePath, context) => {
const relativePath = path.relative(`${context}/dev`, resourcePath);
const [pathName] = relativePath.match(/^[^/]+/);
return `../icon/${pathName}/${name}`;
},
// publicPath: '../icon',
esModule: false,
},
},
],
},
],
},
};
// webpack.dll.js
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
vendors: [
'react',
'react-dom',
'react-router',
'react-router-dom',
'redux',
'react-loadable',
'react-helmet',
'i18next',
'react-i18next',
],
},
output: {
path: path.join(__dirname, '..', 'dll'),
filename: '[name].dll.js',
library: '[name]_library',
},
plugins: [
new CleanWebpackPlugin(),
new webpack.DllPlugin({
path: path.join(__dirname, '..', 'dll/[name]-manifest.json'),
name: '[name]_library',
}),
],
};
// webpack.prod.js
const merge = require('webpack-merge');
const WorkboxPlugin = require('workbox-webpack-plugin');
const common = require('./webpack.common');
module.exports = merge(common, {
plugins: [
new WorkboxPlugin.GenerateSW({
cacheId: 'h5-home',
importWorkboxFrom: 'local',
swDest: 'service-worker.js',
clientsClaim: true,
skipWaiting: true,
exclude: [/\.html$/],
}),
],
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 10000,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `npm.${packageName.replace('@', '')}`;
},
},
},
},
},
});
// webpack.dev.js
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common');
function resolve(dir) {
return path.resolve(__dirname, '..', dir);
}
module.exports = merge(common, {
mode: 'development',
devServer: {
contentBase: resolve('src/static/public'),
},
});