本文介绍webpack3.x的使用
说明,本文前后连贯性很强,建议从头往后看
目录
开始
css文件打包
image文件打包
字体文件打包
json文件打包
csv文件和xml文件打包
多入口文件打包
清理dist目录
development开发环境错误定位
development开发环境开发模式
development开发环境模块热替换
development开发环境模块热替换存在的问题
Tree Shaking(死代码终结者)
development和production代码分离与合并
系统环境变量设置
css文件剥离
代码分割
按需加载
浏览器资源缓存
全局导入
no export导出
静态服务器搭建
-
安装webpack npm init -y npm install webpack --save-dev 使用命令行bundle文件 npx webpack src/index.js dist/bundle.js 使用配置文件bundle文件 项目根目录创建webpack.config.js文件,写入如下内容 const path = require("path"); module.exports = { entry: ‘./src/index.js‘, output: { filename: ‘bundle.js‘, path: path.resolve(__dirname, ‘dist‘) } } 运行命令 npx webpack --config webpack.config.js 或者在package.json中写 "scripts": { "build": "webpack" } 运行 npm run build 即可,默认去找项目根目录的webpack.config.js
-
项目根目录下面一个dist目录用来放打包后的文件,src目录用来放源文件,index.html放在dist目录中,webpack默认这样的 在src/index.js中 import ‘./style.css,让webpack解析此类文件,完成如下几步 首先,安装插件 npm install --save-dev style-loader css-loader 其次,修改webpack.config.js的配置为 const path = require("path"); module.exports = { entry: ‘./src/index.js‘, output: { filename: ‘bundle.js‘, path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /\.css$/, use: [ ‘style-loader‘, ‘css-loader‘ ] } ] } } 最后,运行webpack即可,css文件都打包到bundle.js中,运行时自动插入到header中 提示: css-loader对css文件中的url图片都做了处理,将其替换成打包后的绝对路径
-
在src/index.js中 import MyImage from ‘./4.jpg‘; 其中MyImage是图片的打包后的绝对路径 安装插件,npm install --save-dev file-loader 修改,webpack.config.js 在module中添加如下rules { test: /\.(png|svg|jpg|gif)$/, use: [ ‘file-loader‘ ] } 运行打包即可
-
在css文件中写自定义字体,如 @font-face { font-family: ‘MyFont‘; src: url(‘./my-font.woff2‘) format(‘woff2‘), url(‘./my-font.woff‘) format(‘woff‘); font-weight: 600; font-style: normal; } 同样使用file-loader,修改,webpack.config.js 在module中添加如下rules { test: /\.(woff|woff2|eot|ttf|otf)$/, use: [ ‘file-loader‘ ] } 运行webpack即可
-
webpack默认支持json文件导入,所以无需任何额外操作 在src/index.js中 import data from ‘./index.json‘; console.log(data); index.json中这样定义 { "a": 1, "b": 2 }
-
安装插件 npm install --save-dev csv-loader xml-loader 添加如下的rules { test: /\.(csv|tsv)$/, use: [ ‘csv-loader‘ ] }, { test: /\.xml$/, use: [ ‘xml-loader‘ ] } 使用方式和json文件类似
-
如果你想把你的js文件拆分成小的文件组合,多入口很好的解决这个问题 比如,在src/index.js文件中 import printMe from ‘./print.js‘; 修改 webpack.config.js 配置如下 const path = require("path"); module.exports = { entry: { app: ‘./src/index.js‘, print: ‘./src/print.js‘ }, output: { filename: ‘[name].bundle.js‘, path: path.resolve(__dirname, "dist") } } 修改 dist/index.html <script src="./print.bundle.js"></script> <script src="./app.bundle.js"></script> 打包编译即可 问题:我们必须手动在index.html中插入js文件,可以使用html-webpack-plugin自动插入脚本 安装,npm install --save-dev html-webpack-plugin 修改 webpack.config.js 文件如下 const path = require("path"); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); module.exports = { entry: { app: ‘./src/index.js‘, print: ‘./src/print.js‘ }, plugins: [ new HtmlWebpackPlugin({ title: "自动生成的html文件" }) ], output: { filename: ‘[name].bundle.js‘, path: path.resolve(__dirname, "dist") } } 运行编译即可
-
在打包之前,先清理干净我们的dist输出目录,是非常好的做法 安装 npm install clean-webpack-plugin --save-dev 修改 webpack.config.js 文件 const path = require("path"); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); module.exports = { entry: { app: ‘./src/index.js‘, print: ‘./src/print.js‘ }, plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "自动生成的html文件" }) ], output: { filename: ‘[name].bundle.js‘, path: path.resolve(__dirname, "dist") } } 运行编译即可
-
定位错误信息 所有的文件打包到bundle中,如果代码是错误的,要想定位到错误的源文件,必须使用source-map 修改 webpack.config.js 添加 devtool: ‘inline-source-map‘, 运行打包,如果在浏览器中运行错误,会定位错误源
-
每次查看代码都要运行 npm run build, 然后刷新浏览器,这是很烦的一个操作 webpack有如下几种模式,自动的帮你完成上面两个操作 Watch 模式 首先,在package.json中添加如下命令 "watch": "webpack --watch" 然后运行 npm run watch 当你修改你的代码,webpack自动帮你编译 webpack-dev-server 模式 首先,安装 npm install --save-dev webpack-dev-server 然后,修改 webpack.config.js 在module.exports中添加如下属性 devServer: { contentBase: ‘./dist‘ }, // 作用是告诉webpack在localhost:8080上观察dist目录下面的内容 然后,在package.json中添加如下命令 "start": "webpack-dev-server --open" 运行 npm start 当你修改了你的代码,webpack会自动帮你编译,并且自动帮你刷新浏览器 webpack-dev-middleware 模式 这种模式和webpack-dev-server类似,不过提供了更多的配置 安装 npm install --save-dev express webpack-dev-middleware 修改 webpack.config.js 为如下内容 const path = require("path"); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); module.exports = { entry: { app: ‘./src/index.js‘, print: ‘./src/print.js‘ }, devtool: ‘inline-source-map‘, // devServer: { // contentBase: ‘./dist‘ // }, plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "自动生成的html文件" }) ], output: { filename: ‘[name].bundle.js‘, path: path.resolve(__dirname, "dist"), publicPath: ‘/‘ } } 在项目根目录下添加 server.js,写如下内容,涉及到node.js const express = require(‘express‘); const webpack = require(‘webpack‘); const webpackDevMiddleware = require(‘webpack-dev-middleware‘); const app = express(); const config = require(‘./webpack.config.js‘); const compiler = webpack(config); app.use(webpackDevMiddleware(compiler, { publicPath: config.output.publicPath })) app.listen(3000, function() { console.log("app listenging on port 3000!\n"); }) 在package.json中添加如下命令 "server": "node server.js" 运行 npm run server 即可 打开 localhost:3000 查看
-
热替换,简单理解,就是什么文件修改了,更新什么文件 开启模块热替换webpack方式 修改webpack.config.js文件为 const path = require("path"); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); const webpack = require(‘webpack‘); module.exports = { entry: { // app: ‘./src/index.js‘, // print: ‘./src/print.js‘ app: ‘./src/index.js‘ // 多bundle文件不需要写多个entry实现,HotModuleReplacementPlugin自动完成 }, devtool: ‘inline-source-map‘, devServer: { contentBase: ‘./dist‘, hot: true // 开启热替换 }, plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "自动生成的html文件" }), new webpack.NamedModulesPlugin(), // 在重新编译的时候,会展示被更新模块的相对路径,如 “ [./src/print.js] ./src/print.js 98 bytes {0} [built] ” new webpack.HotModuleReplacementPlugin() ], output: { filename: ‘[name].bundle.js‘, path: path.resolve(__dirname, "dist") } } 运行 npm start 开启模块热替换node.js方式 首先,修改webpack.config.js文件 去掉devServer配置项 const path = require("path"); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); const webpack = require(‘webpack‘); module.exports = { entry: { app: ‘./src/index.js‘ }, devtool: ‘inline-source-map‘, plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "自动生成的html文件" }), new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin() ], output: { filename: ‘[name].bundle.js‘, path: path.resolve(__dirname, "dist") } } 修改server.js const webpack = require(‘webpack‘); const webpackDevServer = require(‘webpack-dev-server‘); const config = require(‘./webpack.config.js‘); const options = { contentBase: ‘./dist‘, hot: true, host: ‘localhost‘ }; webpackDevServer.addDevServerEntrypoints(config, options); const compiler = webpack(config); const server = new webpackDevServer(compiler, options); server.listen(5000, ‘localhost‘, () => { console.log("dev server listening on port 5000"); }) 运行 npm run server css文件的热替换,直接使用style-loader即可自动完成
-
src/index.js内容如下 import printMe from ‘./print.js‘; function fn() { console.log("aaaa") var element = document.createElement(‘div‘); var btn = document.createElement(‘button‘); element.innerHTML = "hahaha"; btn.innerHTML = "按钮"; btn.onclick = printMe; element.appendChild(btn); return element; } document.body.appendChild(fn()); if(module.hot) { // 监听模块热更新 module.hot.accept(‘./print.js‘, function(){ console.log("print.js更新了"); printMe(); }) } src/print.js内容如下 export default function printMe() { console.log("print.js") } 当更新print.js时,点击按钮,输出的信息还是之前的信息,这可以说是webpack热替换的一个硬伤 解决上面这个问题,最原始的方法,如下 if(module.hot) { module.hot.accept(‘./print.js‘, function(){ document.body.removeChild(element); element = fn(); document.body.appendChild(fn); }) }
-
当你的代码中只import某个文件的一部分,但是打包的时候这个文件全部的内容都包含进来了,这就是Tree Shaking要解决的 下载插件 npm install --save-dev uglifyjs-webpack-plugin 将插件添加到webpack.config.js中即可 const UglifyJSPlugin = require(‘uglifyjs-webpack-plugin‘); plugins: [ new UglifyJSPlugin() ] 运行npm run build,只用import了的代码才会被打包 当一个文件的内容被其他的文件导入,然后重新导出,此时Tree Shaking失效了 注:在命令行中 --optimize-minimize 可以实现同样效果
-
将webpack的配置拆分成development和production需要用到webpack-merge插件,作用是合并配置 安装插件 npm install --save-dev webpack-merge 将webpack.config.js文件删除,添加如下三个 webpack.common.js const path = require(‘path‘); const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); module.exports = { entry: { app: ‘./src/index.js‘ }, plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "生成的index.html" }) ], output: { filename: ‘[name].bundle.js‘, path: path.resolve(__dirname, ‘dist‘) } } webpack.dev.js const merge = require(‘webpack-merge‘); const common = require(‘./webpack.common.js‘); module.exports = merge(common, { devtool: ‘inline-source-map‘, devServer: { contentBase: ‘./dist‘ } }); webpack.prod.js const merge = require(‘webpack-merge‘); const UglifyJSPlugin = require(‘uglifyjs-webpack-plugin‘); const common = require(‘./webpack.common.js‘); module.exports = merge(common, { devtool: ‘source-map‘, // 生产环境最好也加上source-map,如果你不需要可以去掉 plugins: [ new UglifyJSPlugin({ sourceMap: true }) ], output: { publicPath: ‘/Test‘ // 指定发布路径 } }); 修改package.json命令 "scripts": { "start": "webpack-dev-server --open --config webpack.dev.js", "build": "webpack --config webpack.prod.js" }
-
利用process.env.NODE_ENV可以方便的设置development和production模式 利用webpack的DefinePlugin插件,可以方便设置此变量 修改webpack.prod.js文件 const merge = require(‘webpack-merge‘); const UglifyJSPlugin = require(‘uglifyjs-webpack-plugin‘); const common = require(‘./webpack.common.js‘); const webpack = require(‘webpack‘); module.exports = merge(common, { devtool: ‘source-map‘, plugins: [ new UglifyJSPlugin({ sourceMap: true }), new webpack.DefinePlugin({ ‘process.env.NODE_ENV‘: JSON.stringify(‘production‘) }) ] }); 然后在你自己的源代码中,随便访问环境变量 if(process.env.NODE_ENV != ‘production‘) { console.log("我不是production模式"); }else { console.log("我是production模式") } 注:在命令行中 --define process.env.NODE_ENV="‘production‘" 可以实现同样效果 利用webpack自己的内置的方式设置环境变量 使用--env自定义环境变量 如,package.json命令 "start": "webpack-dev-server --open --config webpack.dev.js --env.production=false --env.MyEnv=development --progress", 修改webpack.dev.js module.exports = env => { console.log(env); -> 输出 { production: ‘false‘, MyEnv: ‘development‘ } return merge(common, { devtool: ‘inline-source-map‘, devServer: { contentBase: ‘./dist‘ } }); }
-
将css从bundle中剥离出来,需要如下几步 安装 npm install --save-dev extract-text-webpack-plugin 修改 webpack.common.js文件 const path = require(‘path‘); const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { entry: { app: ‘./src/index.js‘ }, module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ // 剥离文件 fallback: "style-loader", use: "css-loader" }), include: path.resolve(__dirname, "src") } ] }, plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "生成的index.html" }), new ExtractTextPlugin({ // 设置插件选项 filename: "styles.css", disable: process.env.NODE_ENV == "developement"?true: false }) ], output: { filename: ‘[name].bundle.js‘, path: path.resolve(__dirname, ‘dist‘) } } 只在production模式中才会剥离css,在development中不剥离从而提高性能
-
代码分割有如下几种方式 多入口,开篇讲过写法 entry: { app: ‘./src/index.js‘, entry: ‘./src/print.js‘ }, 这种写法存在问题,当这些entry中都import了相同的模块,那么打包后的每个bundle中都存在重复的代码 解决办法如下 在webpack.common.js中的plugins项中,添加如下配置 new webpack.optimize.CommonsChunkPlugin({ name: ‘common‘ // 剥离的文件名 }) CommonsChunkPlugin的作用就是将bundle中的重复代码提取到单独的文件中 动态import,单入口 也就是 import() 语法,此语法返回一个promise对象,使用步骤如下 首先,修改webpack.common.js配置如下 const path = require(‘path‘); const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { entry: { app: ‘./src/index.js‘ }, module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) } ] }, plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "生成的index.html" }), new ExtractTextPlugin({ filename: "styles.css", disable: process.env.NODE_ENV == "developement"?true: false }) ], output: { filename: ‘[name].bundle.js‘, chunkFilename: ‘[name].bundle.js‘, // import() 分离多文件的关键配置 path: path.resolve(__dirname, ‘dist‘) } } 然后,假设src/other.js中的代码如下 export function fn () { console.log("相互引用的js"); } 假设src/print.js中的代码如下 export async function GetOtherComponent() { const _ = await import(/* webpackChunkName: "myOther" */ ‘./other.js‘); return _; } 假设src/index.js中的代码如下 import { GetOtherComponent } from ‘./print.js‘; GetOtherComponent().then(data => { data.fn(); console.log(data); }) 运行 npm run build, 输出了myOther.bundle.js文件 代码分离到此结束
-
按需加载还是需要借助import()函数 button.onclick = e => import(/* webpackChunkName: "print" */ ‘./print‘).then(module => { var print = module.default; // 表示print.js中的export.default的对象 print(); });
-
利用webpack的chunkhash可以方便的监测到文件是否修改,如果修改自动更改文件名,浏览器更新资源 使用相当简单,只需要将output改为 output: { // filename: ‘[name].bundle.js‘, filename: ‘[name].[chunkhash].js‘, chunkFilename: ‘[name].bundle.js‘, path: path.resolve(__dirname, ‘dist‘) } 配合CommonsChunkPlugin可以将公共的不变的代码剥离出来,从而让浏览器永远缓存不需要重新下载 修改webpack.common.js配置文件如下 const path = require(‘path‘); const CleanWebpackPlugin = require(‘clean-webpack-plugin‘); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const webpack = require(‘webpack‘); module.exports = { entry: { app: ‘./src/index.js‘, vendor: [ // 设置需要剥离的第三方库 "moment" ] }, module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) } ] }, plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "生成的index.html" }), new ExtractTextPlugin({ filename: "styles.css", disable: process.env.NODE_ENV == "developement"?true: false }), new webpack.optimize.CommonsChunkPlugin({ name: "vendor" // 将vendor入口打包单独输出 }), new webpack.optimize.CommonsChunkPlugin({ name: "manifest" // 打包剩余部分,必须写在最后 }) ], output: { filename: ‘[name].[chunkhash].js‘, path: path.resolve(__dirname, ‘dist‘) } } 存在的问题 webpack打包其中有一个module.id,这个东西是用来标识模块的,从而在剥离的manifest文件中引用 默认情况下module.id是根据模块打包的顺序来自增编号的,当用户打乱了模块import的顺序,那么打 包后所有的文件都被修改了,这是模块的chunkhash都发生改变了,为了解决这个问题,请使用如下方式 只需要将你的webpack.common.js中的plugins修改为 plugins: [ new CleanWebpackPlugin([‘dist‘]), new HtmlWebpackPlugin({ title: "生成的index.html" }), new ExtractTextPlugin({ filename: "styles.css", disable: process.env.NODE_ENV == "developement"?true: false }), new webpack.HashedModuleIdsPlugin(), // 添加这一句话就ok了 new webpack.optimize.CommonsChunkPlugin({ name: "vendor" }), new webpack.optimize.CommonsChunkPlugin({ name: "manifest" }) ],
-
当你需要导入jQuery这样带有全局属性的模块的时候,此部分是你必须掌握的 首先,将jquery下载到项目,修改webpack.common.js文件,在plugins配置项中添加如下 new webpack.ProvidePlugin({ $: ‘jquery‘, jquery: ‘jquery‘ }), 当程序中出现$或者jquery时,webpack在编译的时候就会去找对应的模块 然后,在程序中直接访问即可,如 console.log($); 当然,当你的程序中只用到了其他插件的一部分代码,你想利用tree shaking的技术,可以参考如下 new webpack.ProvidePlugin({ join: [‘lodash‘, ‘join‘] // 表示将lodash中的join方法注册成全局的,webpack只会打包此单个方法 })
-
当你的代码中不export任何代码,如何让其他模块import?,请看下面 首先在 other.js 中定义如下 var obj = { "a": 1, "b": 2, "c": 3 } var a = 1; var fn = function() { console.log(111); } 在 index.js 中导入 import { a,fn,c } from ‘./other.js‘; 安装插件 npm i exports-loader --save 修改webpack.common.js文件,在module中添加如下rules { test: require.resolve(‘./src/other.js‘), use: ‘exports-loader?a,fn,c=obj.c‘ }
-
当我们的代码编译完成之后,如何搭建一个静态服务器来查看我们编译的代码呢? 安装 npm install http-server --save-dev 在package.json文件中添加命令 "server": "http-server dist" 编译代码 npm run build 然后 npm run server 即可 存在的问题,当我们将server停止后,页面将无法访问,解决办法如下 安装 npm install workbox-webpack-plugin --save-dev 将插件配置,写到webpack.config.js中 const WorkboxPlugin = require(‘workbox-webpack-plugin‘); 在plugins配置项中添加 new WorkboxPlugin({ clientsClaim: true, skipWaiting: true }) 在程序入口 index.js 中注册Serveic Worker if (‘serviceWorker‘ in navigator) { window.addEventListener(‘load‘, () => { navigator.serviceWorker.register(‘/sw.js‘).then(registration => { console.log(‘SW registered: ‘, registration); }).catch(registrationError => { console.log(‘SW registration failed: ‘, registrationError); }); }); } 运行 npm run build 直接浏览器访问 http://127.0.0.1:8080 即可