# webpack-html **Repository Path**: dtdths/webpack-html ## Basic Information - **Project Name**: webpack-html - **Description**: webpack multi-main entry. 多入口文件配置 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 2 - **Created**: 2019-01-02 - **Last Updated**: 2022-11-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 一.前言 webpack配置较为繁杂,大部分教程开篇就从基本术语开始讲起,容易让人望而生畏。so,我已实用为目的写了这篇文章。 ## 二.目标 >* 支持热替换 >* 支持scss,自动添加前缀 >* 支持eslint >* 支持babel >* 支持图片压缩 >* 支持多js入口,多html模板 ## 三.搭建环境 1. 创建 package.json 文件 ```bash npm init -y ``` 2. 首先,安装 webpack,webpack-cli 和 cross-env ``` bash npm install --save-dev webpack webpack-cli cross-env ``` 其中 cross-env 主要是用于比较方便的跨平台设置 process.env.NODE_ENV ,用于区分开发环境和生产环境。 3. 新建目录src,目录下创建index.js文件。 4. package.json 下 scripts 中加人 build 命令,更方便的运行 webpack ```json "scripts": { "build": "cross-env NODE_ENV=production webpack" }, 启动 webpack ,并通过 cross-env 设置当前 process.env.NODE_ENV 为 production ,方便启用相应优化 ``` 此时在次目录终端下 ```bash npm run build ``` 就可以将src/index.js打包到dist中。 ## 四.基本配置 此时的webpack运行的是默认配置,只能打包js文件。 要实现其他功能,需要在根目录下创建 webpack.config.js 文件进行配置。 ### 1.entry 和 mode 首先,我们需要配置入口 entry。因为 webpack 是从某一 js 文件开始,打包其中引入的其他文件。为了构建多页面应用程序,我们需要传入一个 js 入口文件组成的对象。为此,我按如下定义 src 目录: ``` |-- src |-- public |-- index.js // 公共模块 |-- pages |-- home |-- index.html // home页 |-- index.js // home页js |-- index.scss // home页js |-- user |-- index.html // user页 |-- index.js |-- index.scss ``` 为了方便读取 src 目录,使用了 glob ```bash npm install --save-dev glob ``` 同时,进行如下配置 ```js const path = require("path"); const glob = require("glob"); const devMode = process.env.NODE_ENV !== 'production'; // 获取 cross-env 设置的 NODE_ENV const entries = ()=>{ const pagesDir = path.resolve(__dirname, 'src/pages'); // pages目录 const entryFiles = glob.sync(pagesDir + '/**/*.{js,ts}'); // glob寻找js文件路径 const reg = /\/src\/pages\/([^/]+).?\/index\.((js)|(ts))/i; return entryFiles.reduce((pv,cv)=>Object.assign(pv,reg.test(cv)?{[RegExp.$1]:cv}:null),{}); // 以pages下目录名为key构成对象 } module.exports = { mode: devMode ? 'development' : 'production', // mode 是 4.x 新增的配置,可以选择 "development","production","none" 是三种值,可以区分开发环境和生产环境。 entry: { ...entries(), // 解构对象 }, } ``` ### 2.output 有了入口,就要有出口。output配置了关于打包后的js输出。 ```js module.exports = { // ... output: { path: path.resolve(__dirname, 'dist'), // 打包后目录 publicPath: '/', // 静态文件目录 filename: 'static/js/[name].[hash:7].min.js', // 定义输出的目录和文件名 }, } ``` 再次运行build命令,会打包出如下目录结构 ``` |-- dist |-- static |-- js |-- home.xxxxx.js // home/index.js打包,并加入了hsah |-- user.xxxxx.js ``` ### 3.使用 html-webpack-plugin 对 html 进行配置 此时 webpack 只打包了js,为了对html进行分别配置,使用 html-webpack-plugin 。此外,还能对 html 进行压缩等优化。 ```bash npm install --save-dev html-webpack-plugin ``` 同时增加配置 ```js const HtmlWebpackPlugin = require('html-webpack-plugin'); // ... const htmlPluginArr = ()=>{ const htmlDir = path.resolve(__dirname, 'src/pages'); const templateFiles = glob.sync(htmlDir + '/**/*.{html,ejs}'); const reg = /\/src\/pages\/([^/]+).?\/index\.((html)|(ejs))/i return templateFiles.filter((filePath)=>reg.test(filePath)).map(filePath => { reg.test(filePath) const filename = RegExp.$1; const baseOption = { filename: `${filename}.html`, //目标文件 template: filePath, chunks: [filename], // 包含的与html同名的chunks代码块 inject: true, // script 插入body底部 minify: { //压缩 removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true // 更多配置 // https://github.com/kangax/html-minifier#options-quick-reference }, // necessary to consistently work with multiple chunks via CommonsChunkPlugin chunksSortMode: 'dependency', //按照依赖顺序引入script } return new HtmlWebpackPlugin(baseOption); }) } // ... module.exports = { // ... plugins: [ ...htmlPluginArr() ], // ... } ``` 再次运行build命令,会打包出如下目录结构 ``` |-- dist |-- static |-- js |-- home.xxxxx.js // home/index.js打包,并加入了hsah |-- user.xxxxx.js |-- home.html // 通过 html-webpack-plugin 自动引入了 home 的 chunk |-- user.html ``` ### 4.devServer 进行完如上配置后,再次打包已经能分别打包出不同的 html,并引入相应的 js 了。为了继续优化配置并验证打包的 js ,我们急需启用支持热更新的本地服务。 ```bash npm install --save-dev webpack-dev-server ``` ```js const webpack = require('webpack'); // ... devServer: { inline: true, // 使用内联模式 clientLogLevel: 'warning', //控制台消息提示 open: true, //自动打开浏览器 //contentBase: false, // 服务器静态文件目录,禁用,使用CopyWebpackPlugin compress: true, // gzip 压缩 disableHostCheck: true, host: "0.0.0.0", port: 8080, useLocalIp: true, // 用本地的ip hot: true, // 启动热更新 overlay: { // 出现错误时在浏览器中显示 warnings: false, errors: true }, proxy: { // 代理配置 // https://github.com/chimurai/http-proxy-middleware "/api": { target: "http://localhost:3000", // pathRewrite: {"^/api" : ""}, //改写请求地址,/api/xxx直接写成/xxx // secure: false, // 支持https } } }, plugins: [ //... new webpack.HotModuleReplacementPlugin(), ], ``` 增加配置后,我们需要在 package.json 里增加 dev 命令 ```bash "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --progress", "build": "cross-env NODE_ENV=production webpack" }, ``` 其中 --progress 代表将运行进度输出到控制台。 配置完成后,执行 npm run dev 命令,就会自动开启本地服务,并打开浏览器。此时,我们就可以进入我们的 html 页面,来实时查看更改。 ### 5.css相关 html 和 js 已经基本配置完成,现在需要对 css 进行配置。 这一部分按照的包会比较多,但是配置都较为简单。 ```bash npm install --save-dev node-sass sass-loader css-loader style-loader postcss-loader autoprefixer postcss-import postcss-url mini-css-extract-plugin ``` 注意: mini-css-extract-plugin 用于 css 文件的提取。在 4.x 之前的版本一般使用 extract-text-webpack-plugin ,在4.x后如果要继续使用需要安装最新板 extract-text-webpack-plugin@next 其中每一个包的功能会在文章最后简单描述 ```js const ExtractTextPlugin = require('extract-text-webpack-plugin'); // ... module.exports = { // ... plugins: [ // ... new MiniCssExtractPlugin({ filename: `static/style/${devMode ? '[name].css' : '[name].[hash].css'}`, chunkFilename: `static/style/${devMode ? '[name].css' : '[name].[hash].css'}`, }) ], module: { rules: [ { test: /\.(sa|sc|c)ss$/, use: [ devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader', ], } ] }, } ``` 这里有必要提一下 loader。rules 中 使用 use 数组来声明加载满足 test 正则的文件。其中 loader 是从右向左运行的,运行的结果会传给下一个 loader。 所以上面配置的意思就是读取 .sass/scss/css 文件,顺序是 sass-loader -> postcss-loader -> css-loader。 由于 mini-css-extract-plugin 提取 css 文件只能用于生产环境,所以最后依据当前环境来判断是使用 style-loader 还是 MiniCssExtractPlugin.loader. 由于使用了 postcss ,我们必须要对其进行配置,来满足 css 的浏览器兼容性。 postcss 也是可以通过配置文件的形式进行配置的。 在根目录新建 .postcssrc.js 文件 ```js // .postcssrc.js module.exports = { // https://github.com/michael-ciniawsky/postcss-load-config "plugins": { "postcss-import": {}, "postcss-url": {}, // to edit target browsers: use "browserslist" field in package.json "autoprefixer": {} } } ``` 具体配置可以查看 postcss 的文档。 在每个入口 js 文件中引入同一目录下的 scss 文件,再次build ``` |-- dist |-- static |-- js |-- home.xxxxx.js // home/index.js打包,并加入了hsah |-- user.xxxxx.js |-- style |-- home.xxxxx.css // home/index.scss打包,并加入了hsah |-- user.xxxxx.css |-- home.html |-- user.html ``` ### 5.其他loader webpack 本身只能打包 js 文件。所以,如上面的 css/scss 文件等其他文件,就需要对应的 loader 去加载使其资源转化。除了 css 文件,我们还需要对字体文件,图片等资源进行加载。并且,为了代码的质量,我们需要使用 eslint 对代码进行规范;为了使用 es6 开发,需要 babel 对代码进行兼容处理。这两个需求都需要用 loader 加载并处理 js 文件。 #### babel 关于[babel配置](https://babel.docschina.org/setup#installation),官网非常的详细。 在 7.x 版本中所有 stage-x presets 已弃用,所有就不在使用次类规范了。 ```bash npm install --save-dev babel-loader @babel/core @babel/preset-env // 如果需要 polyfill npm install --save @babel/polyfill ``` ```js module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] } ``` 关于配置的详细描述的一篇文章 [《你真的会用 Babel 吗?》](https://segmentfault.com/a/1190000011155061) ```js { "presets": [ [ "@babel/preset-env", { "targets": { // 配支持的环境 "browsers": ["> 1%", "last 2 versions", "not ie <= 8"], "node": "current" }, "modules": false, //设置ES6 模块转译的模块格式 默认是 commonjs "debug": false, // debug,编译的时候 console "useBuiltIns": "entry", // 是否开启自动支持 polyfill "include": [], // 总是启用哪些 plugins "exclude": [] // 强制不启用哪些 plugins,用来防止某些插件被启用 } ], ], } ``` #### eslint 以 airbnb 标准为例。 ```bash npm install --save-dev eslint eslint-config-airbnb-base eslint-loader eslint-plugin-import eslint-friendly-formatter babel-eslint eslint-plugin-html ``` ```js module: { rules: [ // ... { enforce: 'pre', // 先使用此 loader 加载 js test: /\.js$/, exclude: /node_modules/, // 第三方库中的代码不进行规范检测 loader: 'eslint-loader', options: { formatter: require('eslint-friendly-formatter'), // 在终端上显示错误 } }, ] }, ``` 与 postcss 类似, eslint 也需要单独的配置。 .eslintignore 文件声明了不进行规范检测的部分 ``` /build/ /config/ /dist/ /*.js ``` .eslintrc.js 文件定义了检测的规范。 ```js module.exports = { root: true, parserOptions: { parser: 'babel-eslint' // babel 处理后的代码的eslint处理 }, env: { browser: true, }, extends: 'airbnb-base', // 所用的规范 globals: { // 可以使用的为在当前 js 内声明的变量 document: true, navigator: true, window:true, node:true }, plugins: [ // 对 html 文件的规则处理 'html' ], rules: { // 除了规预设范外的自定义规则 'import/extensions': ['error', 'always', { js: 'never', }], 'max-len': ['error', { code: 9999, tabWidth: 2 }] } }; ``` 由于 devServer.overlay 的设置,使得 eslint-friendly-formatter 在终端上显示的 error 级别错误出现在了浏览器上,warning 级别错误出现在了浏览器的控制台里,这样更明显的提示了代码的不规范。 #### 图片字体等资源 对图片,字体等资源进行处理,包括图片压缩,小图片转 base64 ```bash npm install --save-dev url-loader file-loader image-webpack-loader ``` ```js modules: { rules: [ // ... { test: /\.((woff2?|svg)(\?v=[0-9]\.[0-9]\.[0-9]))|(woff2?|svg|jpe?g|png|gif|ico)$/, use: [ { loader: 'url-loader', options: { limit: 10240, // 大于10kb将解析成base64 name: 'static/images/[name].[hash:7].[ext]' //输出目录及文件名 } }, { loader: 'image-webpack-loader', options: { // https://github.com/tcoopman/image-webpack-loader mozjpeg: { //压缩jpeg progressive: true, quality: 65 }, optipng: { enabled: false, }, pngquant: { quality: '65-90', speed: 4 }, gifsicle: { interlaced: false, }, webp: { quality: 75 } } } ] }, { test: /\.((ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9]))|(ttf|eot)$/, use: [ { loader: 'url-loader', options: { limit: 10000, name: 'static/fonts/[name].[hash:7].[ext]' } } ] }, ] } ``` 运行 build ,小图片字体会直接转成 bsae64 ,其他的则分别进入 dist/static 下的 images 或 fonts 下 ### 6. resolve 有时引入其他路径下的文件时,相对路径比较复杂。resolve 配置直接解决了这点,让我们更方便的引入。 ```js module.exports = { //... resolve: { extensions: ['.js', '.json'], // 引入时可以省略相应的扩展名 alias: { '@': path.resolve(__dirname, './src') // 使用 @ 代替了 src 文件夹 } }, } ``` 如果使用了 eslint ,此配置会和 eslint 相冲突。为了解决这个问题,我们需要安装 eslint 的一个插件 ```bash npm install --save-dev eslint-import-resolver-webpack ``` 然后在 .eslintrc.js 文件中增加 settings ```js module.exports = { // ... plugins: [ 'html' ], settings: { 'import/resolver': { webpack: { config: 'webpack.config.js' } // 含有resolve的配置文件所在位置 } }, rules: { //... } }; ``` 这样,我们在引入 src 下的文件时,就可以使用 @ 代替一部分相对路径 ```js // old import '../../public/aaa.js' // new import '@/public/aaa' ``` ### 7.常用插件 很多时候我们需要一个静态目录,目录中的内容不经过 webpack 处理。使用 copy-webpack-plugin 将静态文件中的内容完全复制到我们的 dist 下。 ```bash npm install --save-dev copy-webpack-plugin ``` ```js plugins: [ //... new CopyWebpackPlugin([ { from: path.resolve(__dirname, './static'), to: 'static', ignore: ['.*'] } ]), new webpack.DefinePlugin({ // 在js中可直接使用的全局变量 'process.env.NODE_ENV': devMode ? 'development' : 'production', }), ] ``` 配置基本完成,build 和 dev 基本功能有了。 ## 四.optimization (优化) webpack 最重要的功能就是对代码就行优化。4.x 版本新增加了 optimization 配置,以前很多需要插件完成的事现在 webpack 独立就能完成。 ### 1.css 优化 每个入口都引入了当前页面的 css 和公用 css (入 reset.css)。如果按照当前配置 build ,会出现多个 css 文件,并且每个 css 中都包含了 reset.css。 所以,我们需要 1.css 文件合并;2.提取公共 css。 需要使用 optimize-css-assets-webpack-plugin 和 cssnano,来完成 压缩,合并,去重。 ```bash npm install --save-dev optimize-css-assets-webpack-plugin cssnano ``` 首选,我们通过 optimization 的 splitChunks 提取公共模块。然后使用 optimize-css-assets-webpack-plugin 和 cssnano 对 css 进行优化。 ```js module.exports = { plugins: [ new OptimizeCSSAssetsPlugin({ // 参考 https://my.oschina.net/itlangz/blog/2986976 assetNameRegExp: /\.css$/g, cssProcessor: require('cssnano'), cssProcessorPluginOptions: { preset: ['default', { discardComments: { removeAll: true, }, normalizeUnicode: false }] }, canPrint: true }), ] // ... optimization: { splitChunks: { //新版替换webpack.optimize.CommonsChunkPlugin,提取公共模块 cacheGroups: { styles: { name: 'styles', test: /\.scss|css$/, chunks: 'all', // merge all the css chunk to one file enforce: true } } }, }, } ``` 最后,我们需要在 html-webpack-plugin 配置的需要引入的 chunk 数组中增加我们生成的 styles 的 chunk,这样打包后的 html 文件才会正常引入 styles。 ```js const htmlPluginArr = ()=>{ const baseOption = { filename: `${filename}.html`, //目标文件 template: filePath, chunks: [filename,'styles'], } } ``` 再次 build ,会将 css 打包成单个 styles.xxxx.css 文件。 ### 2. js优化 js 代码出去每个入口独有的业务代码之外,还可以大致分为三部分: 1. manifest * >webpack 的 runtime ```js optimization: { runtimeChunk: { name: "manifest" }, }, ``` 将每个打包出来的js文件中的 webpack 相关代码提取成 mainfest 。 2. commons * >公共代码 ```js optimization: { runtimeChunk: { name: "manifest" }, splitChunks: { cacheGroups: { commons: { name: 'commons', // 重复代码打包到commons,和库放在一起 chunks: 'initial', minChunks: 2, enforce:true }, styles: { name: 'styles', test: /\.scss|css$/, chunks: 'all', enforce: true } } }, }, ``` 将重复代码打包为 commons 。 3. vendor * >node_modules 包 ```js optimization: { runtimeChunk: { name: "manifest" }, splitChunks: { // ... vendor: { name: 'vendor', test: chunk => ( chunk.resource && /\.js$/.test(chunk.resource) && /node_modules/.test(chunk.resource) ), chunks: 'initial', }, }, }, ``` 同样,在 html-webpack-plugin 中引入chunk ```js const htmlPluginArr = ()=>{ const baseOption = { filename: `${filename}.html`, template: filePath, chunks: [filename, 'vendor', 'commons', 'manifest','styles'], } } ``` ## 五.分离开发配置与生产配置. 新建配置目录,将配置文件进行细分,删掉根目录下的 webpack.config.js ``` |-- rootDir |-- build |-- build.js // 打包时删除 dist 目录 |-- webpack.base.conf.js // 公共配置 |-- webpack.dev.conf.js // 开发配置 |-- webpack.prod.conf.js // 生产配置 |-- src ``` 提取公共配置到 base 配置中,使用 webpack-merge 进行合并。 并在 build 开始前使用 rimraf 清空旧 dist 目录。 同时修改 npm 命令 ```json "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --progress --config build/webpack.dev.conf.js", "build": "cross-env NODE_ENV=production node build/build.js" }, ``` 别忘了修改 .eslintrc.js 中配置文件的地址 ```js settings: { 'import/resolver': { webpack: { config: 'build/webpack.base.conf.js' } } }, ``` ## 六.常用npm包和其作用
webpack基本
webpack
webpack-cli 可以通过cli使用webpack
webpack-dev-server 用于开启本地服务,代理,热更新
webpack-dev-server 用于合并 webpack 配置
文件相关
file-loader 加载目录下的文件
url-loader 小图片转成base64
image-webpack-loader 图片压缩
copy-webpack-plugin 复制文件到制定目录
html-webpack-plugin 自动生成html并引入css,js
html-loader html片段复用
rimraf 删除文件
css相关
css-loader 加载css
sass-loader 加载sass
node-sass 使用sass-loader的依赖
style-loader 支持把js里引入的css以style标签形式写入html的head
postcss-loader 补全css浏览器前缀
autoprefixer 补全css浏览器前缀所需的插件
postcss-import 解决@import引入路径问提
postcss-url 把css中路径改为打包后路径
mini-css-extract-plugin 把css提取成.css文件
optimize-css-assets-webpack-plugin 用于优化或者压缩CSS资源
cssnano 含优化css规则的包
eslint
eslint eslint依赖
eslint-config-airbnb-base airbnb规则
eslint-loader 使用eslint加载js
eslint-plugin-import 引入相关
eslint-friendly-formatter 终端错误提示
eslint-plugin-html eslint的html相关插件
babel-eslint eslint对babel的支持
eslint-import-resolver-webpack 解决和webpack中resolve的冲突
babel
@babel/core babel依赖
babel-loader 使用babel加载js
@babel/preset-env preset是规范的总结 env是es2015,es2016,es2017的集合
@babel/polyfill 垫片,用于js兼容性
typescript
ts-loader 加载ts文件
typescript ts依赖