不同模块的打包
1.本地项目安装webpack
npm i webpack webpack-cli --save-dev
2.在项目根目录下创建webpack.config.js,在该js中设置打包的mode、entry、otput
module.exports = {
mode: 'development',
entry: path.join(__dirname, 'src/page/test_a/a.js'),
output: {
filename: 'a.js',
path: path.join(__dirname, 'dist/test_a'),
publicPath: './',
libraryTarget: 'umd' // 兼容AMD 和 COMMONJS
},
module: {
rules: [
{ // 处理jsx,先将jsx转换为js,再将es6转换为es5
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [ // 从后往前
'@babel/preset-env', // es6转为es5
'@babel/preset-react' // jsx转为js
]
}
}
]
}, // 最后npm安装,npm i babel-loader @babel/preset-react @babel/preset-env @babel/core --save-dev
{// 处理sass
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader', // style标签插入到html中
{
loader: 'css-loader', // 处理@import等,css 转为js
options: {
importLoaders: 2 // 从后向前执行,已经执行了2个
}
},
'postcss-loader', // 将高级css语法转化为普通css,不过,还需要在根目录下配置一个postcss.config.js
'sass-loader',
]
}, // npm i style-loader css-loader postcss-loader autoprefixer sass-loader node-sass --save-dev
{
test: /\.(jpg|png|gif)$/i,
type: 'asset/resource' // webpack5 之前 file-loader
}, // 基本配置完后,通过npm run build 进行打包,会生成一个dist文件夹,如下图dist
]
}
}
//postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
3.打包完后,在页面结构中,会出现很多style代码
这是因为style-loader将style插入到html中,解决方法是可以使用MiniCssExtractPlugin代替style-loader。
首先安装插件: npm install --save-dev mini-css-extract-plugin;
然后在webpack.config.js中引入: require('min-css-extract-plugin');
在module.exports中进行注册、配置
module.exports = {
plugins: [
new MiniCssExtractPlugin()
],
module: {
rules:[
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
// 'style-loader', // style标签插入到html中
{
loader: 'css-loader', // 处理@import等,css 转为js
options: {
importLoaders: 2 // 从后向前执行,已经执行了2个
}
},
]
},
]
}
}
通过npm run build打包之后,就会在dist目录下打包一个main.css文件;不过,可以通过filename更改文件名
plugins: [
new MiniCssExtractPlugin({
filename: 'a.css'
})
],
再次打包后,会生成a.css文件,此时的页面已经没有样式效果,需要在html中单独引入
<head>
<link rel="stylesheet" href="../../../dist/test_a/a.css"/>
</head>
查看a.css文件,会看到css代码并没有被压缩,可以再引入一个插件来压缩
安装: npm install css-minimizer-webpack-plugin --save-dev
在webpack.config.js中
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'),
module.exports = {
plugins: [
new CssMinimizerPlugin(),
]
}
重新打包后,css会压缩到一行
4.此时会发现main.css 和a.css同时存在,可以引入第三方包,在每次打包之前先清空原来的dist目录。(rimraf)
安装: npm i rimraf --save-dev;在package.json中
"scripts": {
"build": "rimraf ./dist/test_*/ && webpack"
}
- 上述的css和js都是手动引入的,可以通过HtmlWebpackPlugin自动引入;
安装:npm install --save-dev html-webpack-plugin
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: '原html中title的名字',
filename: 'test_a.html', // 新生成的html
template: path.join(__dirname, 'src/page/test_a/test_a.html'), // 原html模板
inject: 'body' // 将js插入到body中
})
]
}
把原来html中的js、css引入删除,修改title
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
在打包后的html中,在body中会插入script标签,script标签中的src属性由publicPath和文件名组成,如果new HtmlWebpackPlugin中没有配置publicPath,则会读取output中的publicPath
<script src="./test_a.js"></script>
- 给文件增加hash值,实现增量部署(版本控制) contenthash 是根据模块内容产生的hash值,只有文件内容改变后hash才会变
module.exports = {
output: {
filename: 'a.[contenthash].js',
},
plugins: [
new MiniCssExtractPlugin({
filename: 'a.[contenthash].css'
})
]
}
7.实现可以通过不同的打包命令打包不同的文件 (npm run build test_a 打包test_a, npm run build test_b 打包test_b)
先引入第三方插件: npm i optimist --save-dev 然后,在webpack.json的命令行中添加env
"scripts": {
"build": "rimraf ./dist/test_*/ &&webpack --env"
}
在webpack.config.js中
var optimist = require('optimist')
var cateName = optimist.args.env;
console.log(cateName) // 使用npm run build 'test_a'时,会打印出'test_a'
// 然后把module.exports中test_a全部替换成cateName
module.exports = {
entry: path.join(__dirname, `src/page/${cateName}/${cateName}.js`)
}
- 实现开发时文件改动时,实时构建,自动刷新浏览器,实现热加载,使用webpack-dev-server
首先将webpack.config.js拆解成三个文件:webpack.config.base.js;webpack.config.build.js; webpack.config.dev.js;
在base.js中
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var optimist = require('optimist');
var cateName = optimist.argv.env;
var entryObj = {};
if (cateName){
entryObj[cateName] = `./src/page/${cateName}/${cateName}.js`
}
module.exports = {
entry: entryObj,
output: {
fileName: '[name].[contenthash].js',
path: path.join(__dirname, `./dist/${cateName}`),
publicPath: './',
chunkFileName:'[name].[contenthash].js',
libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
loader: 'babel-loader',
options: {
plugins: [ // 先执行插件,从前往后执行
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-proposal-class-properties'
],
presets: [ // 后执行preset,从后往前执行
'@babel/preset-env',
'@babel/preset-react'
]
}
]
},
{ // 本地图片的打包处理
test: /\.(png|jpg|gif)$/i,
type: 'assets/resource'
},
]
},
plugins: [
new HtmlWebpackPlugin({
filename: `${cateName}.html`,
template: path.resolve(__dirname, `./src/page/${cateName}/${cateName}.html`),
title: '项目名称',
inject: 'body',
hash: false
})
],
otherInfo: {
cateName
}
}
在build.js中
var {otherInfo, output, plugins = [], ...webpackConfigBase} = require('./webpack.config.base.js');
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
var CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
var TerserPlugin = require('terser-webpack-plugin');
module.exports = {
...webpackConfigBase,
mode: 'production',
output: {
...output,
publicPath: '//cdn...'
},
module: {
rules: [
...webpackConfigBase.module.rules,
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoader: 2
}
},
'postcss-loader',
'sass-loader',
]
}
]
},
plugins: [
newMiniCssExtractPlugin({
filename: `${catenName}.[contenthash].css`
}),
new CssMinimizerPlugin()
]
}
在dev.js中
var {otherInfo = 'test_a', ...webpackConfigBase} = require('./webpack.config.base.js');
module.exports = {
...webpackConfigBase,
mode: 'development',
module: {
rules: [
...webpackConfigBase.module.rules,
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoader: 2
}
},
'postcss-loader',
'sass-loader',
]
}
]
},
devServer: {
port: 1010, // 端口
open: true, // 自动打开浏览器
publicPath: '/', // 资源输出的路径
hot: true // 热更新
}
}
其次,在上面的dev.configh中加入devServer;
然后,在package.json中使用config明确告诉webpack打包时要使用的配置文件
"script:" {
"build": "rimraf ./dist/test_*/ && webpack --config ./webpack.config.build.js --env"
"dev": "webpack serve --config ./webpack.config.dev.js --env"
}
运行npm run dev test_a, 会发现资源都放在根目录下(devServer中的publicPath), 输入localhost:1010/test_a.html 即可看到页面。
其实devServer并没有真的输出资源,而只是把资源放到了内存中。
动态加载
实现点击下一页时才去加载下一页的代码
安装插件: npm i @babel/plugin-syntax-dynamic-import --save-dev
然后配置babel-loader,如下:
最后根据vue或react的特性设置不同的路由
分包实践
原因: bundle打包后,是由我们的业务代码和第三方库构成。而第三方库比较稳定,所以可以单独打包,然后在页面中以cdn的方式引入,并且可以缓存起来,减少用户的请求次数。而业务代码单独打包后,体积也会缩小,打包速度也会加快。
首先可以先通过Webpack Bundle Ananlyzer插件分析各个bundle的大小: npm install --save -dev webpack-bundle-ananlyzer
然后在webpack.config.build.js中:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
运行npm run build即可查看效果
下面,使用splitChunks进行分包
(base.js)
module.exports = {
optimization: {
// 第一种分包方式
splitChunks: {
cacheGroups: {
reactBase: {
name: 'reactBase',
test: (module) => {
return /react/.test(module.context);
},
chunks: 'all',
priority: 1,
},
polyfillBase: {
name: 'polyfillBase',
test: (module) => {
return /core|babel|es6|promise/.test(module.context);
},
chunks: 'all',
priority: 2,
},
fetchBase: {
name: 'fetchBase',
test: (module)=> {
return /fetch|jsonp/.test(module.context);
},
chunks: 'all',
priority: 3,
},
}
},
}
}
运行npm run build之后即可看到打包效果。但打包时长并没有太大的变化。下面看手动分包,将业务代码和第三方代码分开打包。
首先,新建一个webpack.config.dll.js,用来打包第三方包
const path = require('path');
const webpack = require('webpack');
var TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
react: [
'react',
'react-dom',
'react-router',
'react-router-dom',
],
polyfill: [
'@babel/polyfill',
'es6-promise'
],
common: [
'fetch-jsonp',
'whatwg-fetch',
'classnames',
'prop-types',
'better-scroll'
],
},
output: {
path: path.resolve('./dist/vendor'),
filename: '[name].[contenthash].js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve('./dist/vendor', '[name]-mainfest.json'),
name: '[name]_library'
}),
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
extractComments: false
})
]
}
}
然后在package.json中
"scripts": {
"build:dll": "rimraf ./dist/vendor && webpack --config webpack.config.dll.js"
}
在webpack.config.base.js中
module.expors = {
plugins: [
new Wepck.DllReferencePlugin({
maifest: require('./dist/vendor/common-mainfest.json')
}),
new Wepck.DllReferencePlugin({
maifest: require('./dist/vendor/react-mainfest.json')
}),
new Wepck.DllReferencePlugin({
maifest: require('./dist/vendor/polyfill-mainfest.json')
}),
]
}
最后需要在html中通过script的方式引入
第三种分包方式: 在config中排除第三方包,在html中通过script的方式引入cdn链接
module.exports = {
externals: {
'react-router-dom': 'ReactRouterDOM',
'react': 'React',
'react-dom': 'ReactDom'
}
}