Webpack 初识
1. 引例:前端技术的发展历程
-
第一代:所有的代码写在一个文件中
- index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>第一代前端开发</title> <link rel="stylesheet" href="./index.css"> </head> <body> <div id="root"></div> <script src="./index.js"></script> </body> </html> - index.css
* { padding: 0; margin: 0; } - index.js
var dom = document.getElementById("root"); var header = document.createElement("div"); header.innerHTML = "This is header"; dom.append(header); var content = document.createElement("div"); content.innerHTML = "This is content"; dom.append(content); var footer = document.createElement("div"); footer.innerHTML = "This is footer"; dom.append(footer);
缺点:所有代码写在一个文件中,当项目工程变大时,代码文件会变得很大很复杂,难以维护和扩展,复用性很差
- index.html
-
第二代:使用面向对象的思维,代码拆分写在不同的文件中
- index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>第二代前端开发</title> <link rel="stylesheet" href="./index.css"> </head> <body> <div id="root"></div> <script src="index.js"></script> <script src="./header.js"></script> <script src="./content.js"></script> <script src="./footer.js"></script> </body> </html> - index.css
* { padding: 0; margin: 0; } - header.js
function Header() { var header = document.createElement("div"); header.innerHTML = "This is header"; dom.append(header); } - content.js
function Content() { var content = document.createElement("div"); content.innerHTML = "This is content"; dom.append(content); } - footer.js
function Footer() { var footer = document.createElement("div"); footer.innerHTML = "This is footer"; dom.append(footer); } - index.js
var dom = document.getElementById("root"); new Header(); new Content(); new Footer();
优点:进行模块化划分
缺点:
① 在 index.html 中引入了多个 js 文件,即要发送多个 http 请求去获取这些文件,所以整个页面的加载速度会变慢
② 文件之间的依赖关系不直观
③ 文件引入顺序不同则可能导致出错
- index.html
-
第三代:使用 ES module 模块化开发
-
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>第三代前端开发</title> <link rel="stylesheet" href="./index.css"> </head> <body> <div id="root"></div> <script src="./dist/main.js"></script> </body> </html> -
index.css
* { padding: 0; margin: 0; } -
header.js
function Header() { var dom = document.getElementById("root"); var header = document.createElement("div"); header.innerHTML = "This is header"; dom.append(header); } export default Header; -
content.js
function Content() { var dom = document.getElementById("root"); var content = document.createElement("div"); content.innerHTML = "This is content"; dom.append(content); } export default Content; -
footer.js
function Footer() { var dom = document.getElementById("root"); var footer = document.createElement("div"); footer.innerHTML = "This is footer"; dom.append(footer); } export default Footer; -
index.js
import Header from "./header.js"; import Content from "./content,js"; import Footer from "./footer.js"; new Header(); new Content(); new Footer();
好处:模块化开发,解决了依赖不清晰和要求导入顺序的问题
注意:浏览器并不能直接识别ES6模块系统,所以需要一个工具来将模块和主文件打包在一起,合并成一个文件,该文件浏览器能直接识别解析
-
2. Webpack的作用与定位
- Webpack是一个模块打包工具,即Webpack能够识别和解析JS的模块,将多个模块文件打包成一个浏览器能够解析的JS文件,从而解决浏览器不能解析模块化JS代码的问题
- Webpack不仅仅可以识别ES6-Module,也还可以识别CommonJS,CMD,ADM风格的模块
- 最开始Webpack只是JavaScript的打包工具,即只能进行CommonJS、CMD、AMD和ES6-Module的打包工作,但随着发展,现在能打包 css,less,sass 以及图片等等许多类型的文件
3. Webpack的安装
- 创建npm工程项目
npm init - 查看npm上webpack的全部版本
yarn info webpack - 全局安装
yarn global add webpack webpack-cli - 查看全局webpack版本
webpack -v - 当前项目工程安装
yarn add webpack webpack-cli - 安装特定版本
yarn add webpack@3.6.0 - 查看当前项目工程webpack版本
// npx命令是在当前项目工程的node_modules中寻找webpack // 有则使用,无则下载后再使用 npx webpack -v - 推荐不使用全局安装:因为不同项目所使用的webpack版本可能是不同的
- webpack-cli的作用:能够在命令行中使用webpack命令和npx webpack命令
4. Webpack配置文件 [mode, entry, output]
- Webpack配置文件本质是CommonJS的模块,导出的对象描述了Webpack配置信
const path = require("path"); module.exports = { mode: "production(默认)|development", entry: { main: "./index.js" }, output: { filename: "bundle.js", // 必须写绝对路径,通过 path 模块完成 path: "path.resolve(__dirname, 'bundle')" } } - mode: 打包模式
- 生产模式(production): 默认值,生产模式会压缩打包后文件
- 开发模式(development): 开发模式不会压缩打包后的文件
- entry: 入口文件
- main: 打包后的文件名
- "./index.js": 进行打包的入口文件
- output: 输出打包文件
- filename: 打包入口文件的名字
- chunkFilename: 打包辅助文件的名字
- path: 打包文件的存放路径,需要使用绝对路径
5. Webpack进行打包
-
配好配置文件后,执行
npx webpack命令即可进行打包- 如果没有自定义的Webpack配置文件,则Webpack会使用默认配置进行打包
- Webpack能自动识别的配置文件的名字为
webpack.config.js - 如果配置文件不叫
webpack.config.js,则需要加上--config 配置文件路径npx webpack --config my.js - env环境变量:在执行webpack打包命令时,可以通过设置环境变量env,将特定的参数传递到webpack.config.js文件中使用
webpack --env.mod=production webpack.config.js// webpack.config.js console.log(env.production); // production -
配置npm脚本
// package.json { "script": { "bundle": "npx webpack --config webpack.config.js" } }- 可以使用
npm run bundle使用执行webpack命令
- 可以使用
-
打包完成后的提示信息
信息 说明 Hash 哈希值,本次打包的唯一标识 Version webpack的版本 Time 打包耗时 Asset 打包出来的文件 Size 打包出来的文件的大小 Chunks 打包出来的文件的序号 Chunks 打包出来的文件的别称 -
打包多个文件,并指定使用前缀
module.exports = { entry: { main: "./src/index.js", sub: "./src/index.js" }, output: { publicPath: 'http://cdn.com.cn' filename: [name].js } }- entry对象中可以放多个属性,每个属性名为打包文件名字,属性值为源文件路径
- output中可以指定使用前缀,即在入口文件引入打包文件时,需要加上该前缀
6. Chunk
- 打包后生成的每个js文件称为一个Chunk
7. 打包过程分析
- 在打包时,可以导出打包过程描述文件
webpack --config my.config.js > state.json - 可以将描述文件上传到Webpack官网提供的分析工具,进行分析
http://webpack.github.com/analyse
Loader [module]
1. Loader是什么
- Loader是一种打包方案,它告诉Webpack针对某种类型的文件,如何进行打包
2. 为什么需要Loader
- Webpack默认情况下,只能打包JavaScript的模块文件,即Webpack只能打包后缀名为.js的文件
- 为了让Webpack能够打包其他类型的文件,比如打包图片,我们需要定义其他文件的打包方案(即Loader),并且告诉Webpack如何使用(即写配置文件)
3. 模块打包配置
- 例子
const path = require("path"); module.exports = { entry: { main: "./index.js" }, output: { filename: "bundle.js", path: "path.resolve(__dirname, 'bundle')" // 必须写绝对路径,通过 path 模块完成 }, mode: "production(默认)|development", module: { rules: [ { test: /\.jpg$/, use: [ { loader: "file-loader" } ] }, { test: /\.vue$/, use: [ { loader: "vue-loader" } ] } ] } } - module: 模块打包配置
- rules: 打包规则,值是一个数组,数组内存对象,每个对象描述一个规则
- test: 模块名称匹配的特征(使用正则表达式)
- use: 使用的打包方案,值是一个数组,数组内存方案描述对象
- rules: 打包规则,值是一个数组,数组内存对象,每个对象描述一个规则
4. file-loader
-
功能:告诉Webpack如何打包静态文件,包括打包字体文件
-
安装file-loader
yarn add file-loader -
基础使用
module.exports = { rules: [ { test: /\.(jpg|png|gif)$/, use: [ { loader: "file-loader" } ] } ] } -
选项
module.exports = { rules: [ { test: /\.jpg$/, use: [ { loader: "file-loader", options: { name: '[name]_[hash].[ext]', outputPath: 'images/' } } ] } ] }- options: 使用选项
- name: 打包后静态文件的名字
- outputPath: 打包后静态文件存放的路径
- options: 使用选项
-
占位符
- [name]: 原始文件的名称
- [hash]: 原始文件的哈希值
- [ext]: 原始文件的后缀名
- [contenthash]: 打包文件内容的哈希值,一般用于阻止浏览器缓存
5. url-loader
-
作用
- 能够实现file-loader的全部功能
- 增强功能是url-loader会将图片转为base64,所以只适合打包小图片文件
-
选项
module.exports = { rules: [ { test: /\.jpg$/, use: [ { loader: "file-loader", options: { name: '[name]_[hash].[ext]', outputPath: 'images/', limit: 2000 } } ] } ] }- limit: 大小限制,只有文件大小小于2kb时才会使用该Loader打包
- name: 打包后静态文件的名字
- outputPath: 打包后静态文件存放的路径
6. css-loader和style-loader
- 功能
- css-loader功能:解析css的依赖关系,合并成一个css文件
- style-loader功能:将合并后的css文件挂载到header标签中
- 两个的功能合起来:打包css类型的文件
- 例子
module.exports = { rules: [ { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { importLoaders: 2, modules: true } } ] } ] }- 使用Loader时:会从后往前使用数组中描述的Loader,即先使用css-loader,再使用style-loader
importLoaders: 2: 表示执行该loader前还要执行后面的两个loadermodules: true: 开启css的模块化打包,避免全局css
7. sass-loader
- 安装
yarn add sass-loader node-sass - 使用
module.exports = { rules: [ { test: /\.css$/, use: [ { "style-loader" }, { "css-loader" },{ "scss-loader" } ] } ] }- 使用Loader时:会从后往前使用数组中描述的Loader,即先使用sass-loader,再使用css-loader,再使用style-loader
Plugin [plguins]
1. Plugin是什么
- plugin是插件,它可以在webpack运行的某一个时刻,帮你自动做一些事情
2. html-webpack-plugin
- 功能:在打包结束后会自动生成一个html文件(入口文件),并把打包后的js文件自动引入到这个html文件中
- 安装
yarn add html-webpack-plugin - 使用
// 导入html-webpack-plugin插件 const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); module.exports = { mode: development, entry: { main: index.js }, output: { name: "bundle.js", path: path.resolve(__dirname, "bundle") }, plugins: [ new HtmlWebpackPlugin({ template: "./template.html" }) ] }- plugins: 配置插件,是一个数组,里面存插件对象
- 新建HtmlWebpackPlugin时,可以指定使用自定义模板,则生成入口html文件时,会以该模板作为基础生成
3. clean-webpack-plugin
- 功能:打包时,会先清空生成目录中的文件
- 安装
yarn add clean-webpack-plugin - 使用
// 导入clean-webpack-plugin插件 const CleanWebpackPlugin = require("clean-webpack-plugins") // 导入html-webpack-plugin插件 const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); module.exports = { mode: development, entry: { main: index.js }, output: { name: "bundle.js", path: path.resolve(__dirname, "bundle") }, plugins: [ new HtmlWebpackPlugin({ template: "./template.html" }), new CleanWebpackPlugin("生成目录") ] }
4. ProviderPlugin
- 功能:webpackn能够识别特定的字符串,然后自动进行模块导入,而不用自己写import...from...语句,这称为垫片(shimming)
- 使用
const webpack = require("webpack"); module.exports = { plugins: [ new webpack.ProviderPlugin({ $: "jquery", _join: ['lodash', 'join'] }) ] }- 当代码中出现 $ 这个字符串时,webpack会自动导入jQuery,而不用自己写
import $ from "jquery"; - 当代码中出现"_join"这个字符串时,webpack会自动导入lodash,将"_join"替换为lodash.join,而不用自己写
import _ from "lodash"; _.join();
- 当代码中出现 $ 这个字符串时,webpack会自动导入jQuery,而不用自己写
source-map [devtool]
1. source-map定义
- source-map是一个映射关系文件,存储了打包文件中的语句与源文件中语句的对应关系
2. source-map作用
- 在报错时,会提示源文件的错误行数,而不是打包文件中的错误行数
3. source-map使用
- 通过设置devtool的值来开启source-map
module.exports = { devtool: "source-map" }
4. source-map属性值
- none: 不使用source-map
- source-map: 生成.map文件,报错精确到某行某列
- inline: map信息写入到打包文件中
- cheap: 报错只精确到某行,不精确到某列
- module: 只管源代码,不管node_modules中的代码
- eval: 通过eval函数来实现映射,速度快
5. 最佳实践:
- 开发环境:cheap-module-eval-source-map
- 生产环境:cheap-module-source-map
自动刷新 [devServer]
1. 方法一:使用--watch参数
- 功能:监听文件状态,一旦发生改变,则自动重新打包
- 缺点:无法自动打开浏览器,无法自动刷新页面,无法模拟服务器环境(不可以用ajax请求)
- 使用:在执行webpack命令时,加上--watch参数
"script" : { "watch": "npx webpack --watch" }
2. 方法二:WebpackDevServer
- 功能:监听文件状态,一旦发生改变
- 优点:自动刷新浏览器,模拟了服务器(即可以用ajax请求)
- 使用:添加devServer配置
- contentBase: 服务器服务的目录路径
- open: 是否自动打开浏览器
- prot: 设置端口
- proxy: 设置代理
export.module = { devServer: { contentBase: "./dist", open: true // 自动打开浏览器,并访问地址 prot: 61006, proxy: { "api": "http://localhost:3000" } } }"script" : { "watch": "npx webpack --watch" "start": "npx webpack-dev-server" }
3. Hot Module Replacement(HMR)
- 说明:热模块重载,需要通过一个插件,配合devServer使用
- 功能:只重新加载发生改变的文件,未改变的文件
- 比如更改了css,就只更新css,而不会重新刷新页面,因为index.js没有发生改变,即方便调试css
- 使用
const webpack = require("webpack"); module.exports = { plugins: [ new webpack.HotModuleReplacementPlugin() ], devServer: { hot: true, // 开启热模块替换 hotOnly: true // 开启热模块替换 } }- hot: 是否开启HMR
- hotOnly: 是否当HMR失败时也不执行其他操作(刷新全部页面)
Webpack增强功能 [optimazation]
1. Tree Shaking
- 作用:打包模块时,只打包模块中使用到的对象,即按需打包
- 前提:Tree Shaking只支持ES Module,因为ES Module是静态引入
- 配置
// webpack.config.js // mode: development module.exports = { optionization: { usedExports: true } } // package.json "siderEffects": ["@babel/polly-fill", "*.css"] - 注意事项
- 在development模式下,即使使用了Tree Shaking,也只会在打包文件中以注释形式提示只导出了某些对象,但实际还是会导出全部对象,这是为了避免在调试时,source-map发生错误
- 在producion模式下,使用了Tree Shaking后,只会导出实际使用到的对象
- 在production模式下,默认开启了Tree Shaking,optionization配置可以不写
- sideEffecets是为了避免Webpack不打包那种只导入而不使用的模块。比如CSS文件
2. Code Splitting
- 定义:代码分割,将一个js文件分开为多个文件
- 作用:当页面业务逻辑发生变化时,会重新加载js文件,而如果没有发生改变的文件由于缓存的存在,而不用重新下载,这样能提高性能
- 使用
module.exports = { optimazation: { splitChunks: "all" } }
Webapck高级功能
1. 配置文件模块化
- 将配置文件进行抽离
- 工程目录
- webpack.common.js
- webpack.dev.js
- webpack.prod.js
- package.json
{ "scripts": { "dev": "webpack-dev-server --config webpack.dev.js", "build": "webpack --config webpack.prod.js" } }
- 工程目录
- 将配置进行合并
- 安装webpack-merge
yarn add webpack-merge - 使用webpack-merge
// webpack.dev.js const merge = require("webpack-merge"); const commonConfig = require("./webpack.common.js"); const devConfig = {} module.exports = merge(commonConfig, devConfig); // webpack.prod.js const merge = require("webpack-merge"); const commonConfig = require("./webpack.common.js"); const prodConfig = {} module.exports = merge(commonConfig, prodConfig);
- 安装webpack-merge
2. Lazy Loading、webpackPreloading和webpackPrefetching
- JavaScript加载:指的是下载并执行JavaScript代码
- Lazy Loading
- 定义:懒加载,即按需加载,默认不加载全部的JavaScript,而是当需要使用时才加载相应的JavaScript
- 使用:需要使用ES6中的import语句
- 通过Promise方式
// 点击时才加载click模块中的代码 document.addEventListener("clik", () => { import("./click.js").then( ({default, func}) => { func(); } ); }); - 通过async/await方式
// 点击时才加载click模块中的代码 async function Click() { const { default, func } = await import("./click.js"); func(); } document.addEventListener("clik", Click);
- 通过Promise方式
- webpackPrefetching
- 使用:用在import语句中
- 作用:在核心代码加载完成后,空闲时主动进行异步加载,弥补了懒加载的缺点
- 对比:懒加载是使用时才进行加载,而webpackPrefecting是空闲时进行加载,这样能提高性能
document.addEventListener("clik", () => { import(/* webpakPrefetch: true */ "./click.js") .then(({default,func}) => { func(); }); } );
- webpackPreloading
- 使用:用在import语句中
- 作用:在核心代码加载时也一起加载
- 对比:懒加载是使用时才进行加载,而webpackPreloading是与核心代码一起进行加载
document.addEventListener("clik", () => { import(/* webpakPreloading: true */ "./click.js") .then(({default,func}) => { func(); }); } );
3. Webpack与浏览器缓存
- 在文件名字上加上文件内容的哈希值,从而在文件发生改变时,防止浏览器缓存的影响
output: { filename: '[name].[contenthash].js', chunkFilename: '[name].[contenthash].js' }
Webpack使用常见工具
- Babel处理ES6
- // TODO