初学者应该看的 Webpack 完整指南(2020)

开发 前端
如今,CLI工具(如create-react-app或Vue -cli)已经为我们抽象了大部分配置,并提供了合理的默认设置。

[[335456]]

我们应该学习 webpack 吗 ?

如今,CLI工具(如create-react-app或Vue -cli)已经为我们抽象了大部分配置,并提供了合理的默认设置。

即使那样,了解幕后工作原理还是有好处的,因为我们迟早需要对默认值进行一些调整。

在本文中中,我们会知道 webpack可以做什么,以及如何配置它以满足我们的日常需求。

什么是 webpack?

作为前端开发人员,我们应该熟悉 module 概念。你可能听说过 AMD模块,UMD,Common JS还有ES模块。

webpack是一个模块绑定器,它对模块有一个更广泛的定义,对于webpack来说,模块是:

  • Common JS modules
  • AMD modules
  • CSS import
  • Images url
  • ES modules

webpack 还可以从这些模块中获取依赖关系。

webpack 的最终目标是将所有这些不同的源和模块类型统一起来,从而将所有内容导入JavaScript代码,并最生成可以运行的代码。

entry

Webpack的 **entry(入口点)**是收集前端项目的所有依赖项的起点。实际上,这是一个简单的 JavaScript 文件。

这些依赖关系形成一个依赖关系图。

Webpack 的默认入口点(从版本4开始)是src/index.js,它是可配置的。webpack 可以有多个入口点。

Output

output是生成的JavaScript和静态文件的地方。

Loaders

Loaders 是第三方扩展程序,可帮助webpack处理各种文件扩展名。例如,CSS,图像或txt文件。

Loaders的目标是在模块中转换文件(JavaScript以外的文件)。文件成为模块后,webpack可以将其用作项目中的依赖项。

Plugins

插件是第三方扩展,可以更改webpack的工作方式。例如,有一些用于提取HTML,CSS或设置环境变量的插件。

Mode

webpack 有两种操作模式:开发(development)和生产(production)。它们之间的主要区别是生产模式自动生成一些优化后的代码。

Code splitting

代码拆分或延迟加载是一种避免生成较大包的优化技术。

通过代码拆分,开发人员可以决定仅在响应某些用户交互时加载整个JavaScript块,比如单击或路由更改(或其他条件)。

被拆分的一段代码称为 chunk。

Webpack入门

开始使用webpack时,先创建一个新文件夹,然后进入该文件中,初始化一个NPM项目,如下所示:

  1. mkdir webpack-tutorial && cd $_ 
  2.  
  3. npm init -y 

接着安装 webpack,webpack-cli和 webpack-dev-server:

  1. npm i webpack webpack-cli webpack-dev-server --save-dev 

要运行 webpack,只需要在 package.json 配置如下命令即可:

  1. "scripts": { 
  2.    "dev""webpack --mode development" 
  3.  }, 

通过这个脚本,我们指导webpack在开发模式下工作,方便在本地工作。

Webpack 的第一步

在开发模式下运行 webpack:

  1. npm run dev 

运行完后会看到如下错误:

  1. ERROR in Entry module not found: Error: Can't resolve './src' 

webpack 在这里寻找默认入口点src/index.js,所以我们需要手动创建一下,并输入一些内容:

  1. mkdir src 
  2.  
  3. echo 'console.log("Hello webpack!")' > src/index.js 

现在再次运行npm run dev,错误就没有了。运行的结果生成了一个名为dist/的新文件夹,其中包含一个名为main.js的 JS 文件:

  1. dist 
  2. └── main.js 

这是我们的第一个webpack包,也称为output。

配置 Webpack

对于简单的任务,webpack无需配置即可工作,但是很快我们就会遇到问题,一些文件如果没有指定的 loader 是没法打包的。所以,我们需要对 webpack进行配置,对于 webpack 的配置是在 webpack.config.js 进行的,所以我们需要创建该文件:

  1. touch webpack.config.js 

Webpack 用 JavaScript 编写,并在无头 JS 环境(例如Node.js)上运行。在此文件中,至少需要一个module.exports,这是的 Common JS 导出方式:

  1. module.exports = { 
  2.   // 
  3. }; 

在webpack.config.js中,我们可以通过添加或修改来改变webpack的行为方式

  • entry point
  • output
  • loaders
  • plugins
  • code splitting

例如,要更改入口路径,我们可以这样做

  1. const path = require("path"); 
  2.  
  3. module.exports = { 
  4.   entry: { index: path.resolve(__dirname, "source""index.js") } 
  5. }; 

现在,webpack 将在source/index.js中查找要加载的第一个文件。要更改包的输出路径,我们可以这样做:

  1. const path = require("path"); 
  2.  
  3. module.exports = { 
  4.   output: { 
  5.     path: path.resolve(__dirname, "build"
  6.   } 

这样,webpack将把最终生成包放在build中,而不是dist.(为了简单起见,在本文中,我们使用默认配置)。

打包 HTML

没有HTML页面的Web应用程序几乎没有用。要在webpack中使用 HTML,我们需要安装一个插件html-webpack-plugin:

  1. npm i html-webpack-plugin --save-dev 

一旦插件安装好,我们就可以对其进行配置:

  1. const HtmlWebpackPlugin = require("html-webpack-plugin"); 
  2. const path = require("path"); 
  3.  
  4. module.exports = { 
  5.   plugins: [ 
  6.     new HtmlWebpackPlugin({ 
  7.       template: path.resolve(__dirname, "src""index.html"
  8.     }) 
  9.   ] 
  10. }; 

这里的意思是让 webpack,从 src/index.html 加载 HTML 模板。

html-webpack-plugin的最终目标有两个:

  • 加载 html 文件
  • 它将bundle注入到同一个文件中

接着,我们需要在 src/index.html 中创建一个简单的 HTML 文件:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>Webpack tutorial</title> 
  6. </head> 
  7. <body> 
  8.  
  9. </body> 
  10. </html> 

 

稍后,我们会运行这个程序。

webpack development server

在本文第一部分中,我们安装了webpack-dev-server。如果你忘记安装了,现在可以运行下面命令安装一下:

  1. npm i webpack-dev-server --save-dev 

webpack-dev-server 可以让开发更方便,不需要改动了文件就去手动刷新文件。配置完成后,我们可以启动本地服务器来提供文件。

要配置webpack-dev-server,请打开package.json并添加一个 “start” 命令:

  1. "scripts": { 
  2.   "dev""webpack --mode development"
  3.   "start""webpack-dev-server --mode development --open"
  4. }, 

有了 start 命令,我们来跑一下:

  1. npm start 

运行后,默认浏览器应打开。在浏览器的控制台中,还应该看到一个 script 标签,引入的是我们的 main.js。

 

使用 webpack loader

Loader是第三方扩展程序,可帮助webpack处理各种文件扩展名。例如,有用于 CSS,图像或 txt 文件的加载程序。

下面是一些 loader 配置介绍:

  1. module.exports = { 
  2.   module: { 
  3.     rules: [ 
  4.       { 
  5.         test: /\.filename$/, 
  6.         use: ["loader-b""loader-a"
  7.       } 
  8.     ] 
  9.   }, 
  10.   // 
  11. }; 

相关配置以module 关键字开始。在module内,我们在rules内配置每个加载程序组或单个加载程序。

对于我们想要作为模块处理的每个文件,我们用test和use配置一个对象

  1.     test: /\.filename$/, 
  2.     use: ["loader-b""loader-a"

test 告诉 webpack “嘿,将此文件名视为一个模块”。use 定义将哪些 loaders 应用于些打包的文件。

打包 CSS

要 在webpack 中打包CSS,我们需要至少安装两个 loader。Loader 对于帮助 webpack 了解如何处理.css文件是必不可少的。

要在 webpack 中测试 CSS,我们需要在 src 下创建一个style.css文件:

  1. h1 { 
  2.     color: orange; 

另外在 src/index.html 添加 h1 标签

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>Webpack tutorial</title> 
  6. </head> 
  7. <body> 
  8. <h1>Hello webpack!</h1> 
  9. </body> 
  10. </html> 

 

最后,在src/index.js 中加载 CSS:

在测试之前,我们需要安装两个 loader:

  • css-loader:解析 css 代码中的 url、@import语法像import和require一样去处理css里面引入的模块
  • style-loader:帮我们直接将css-loader解析后的内容挂载到html页面当中

安装 loader:

  1. npm i css-loader style-loader --save-dev 

然后在webpack.config.js中配置它们

  1. const HtmlWebpackPlugin = require("html-webpack-plugin"); 
  2. const path = require("path"); 
  3.  
  4. module.exports = { 
  5.   module: { 
  6.     rules: [ 
  7.       { 
  8.         test: /\.css$/, 
  9.         use: ["style-loader""css-loader"
  10.       } 
  11.     ] 
  12.   }, 
  13.   plugins: [ 
  14.     new HtmlWebpackPlugin({ 
  15.       template: path.resolve(__dirname, "src""index.html"
  16.     }) 
  17.   ] 
  18. }; 

现在,如果你运行npm start,会看到样式表加载在HTML的头部:

 

一旦CSS Loader 就位,我们还可以使用MiniCssExtractPlugin提取CSS文件

Webpack Loader 顺序很重要!

在webpack中,Loader 在配置中出现的顺序非常重要。以下配置无效:

  1. // 
  2.  
  3. module.exports = { 
  4.   module: { 
  5.     rules: [ 
  6.       { 
  7.         test: /\.css$/, 
  8.         use: ["css-loader""style-loader"
  9.       } 
  10.     ] 
  11.   }, 
  12.   // 
  13. }; 

此处,“style-loader”出现在 “css-loader” 之前。但是style-loader用于在页面中注入样式,而不是用于加载实际的CSS文件。

相反,以下配置有效:

  1. module.exports = { 
  2.   module: { 
  3.     rules: [ 
  4.       { 
  5.         test: /\.css$/, 
  6.         use: ["style-loader""css-loader"
  7.       } 
  8.     ] 
  9.   }, 
  10.   // 
  11. }; 

webpack loaders 是从右到左执行的。

打包 sass

要在 webpack 中测试sass,同样,我们需要在 src 目录下创建一个 style.scss 文件:

  1. @import url("https://fonts.googleapis.com/css?family=Karla:weight@400;700&display=swap"); 
  2.  
  3. $font: "Karla", sans-serif; 
  4. $primary-color: #3e6f9e; 
  5.  
  6. body { 
  7.   font-family: $font; 
  8.   color: $primary-color; 

另外,在src/index.html中添加一些 Dom 元素:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>Webpack tutorial</title> 
  6. </head> 
  7. <body> 
  8.   <h1>Hello webpack!</h1> 
  9.   <p>Hello sass!</p> 
  10. </body> 
  11. </html> 

 

最后,将 sass 文件加载到src/index.js中:

  1. import "./style.scss"
  2. console.log("Hello webpack!"); 

在测试之前,我们需要安装几个 loader:

  • sass-loader:加载 SASS / SCSS 文件并将其编译为 CSS
  • css-loader:解析 css 代码中的 url、@import语法像import和require一样去处理css里面引入的模块
  • style-loader:帮我们直接将css-loader解析后的内容挂载到html页面当中

安装 loader:

  1. npm i css-loader style-loader sass-loader sass --save-dev 

然后在webpack.config.js中配置它们:

  1. const HtmlWebpackPlugin = require("html-webpack-plugin"); 
  2. const path = require("path"); 
  3.  
  4. module.exports = { 
  5.   module: { 
  6.     rules: [ 
  7.       { 
  8.         test: /\.scss$/, 
  9.         use: ["style-loader""css-loader""sass-loader"
  10.       } 
  11.     ] 
  12.   }, 
  13.   plugins: [ 
  14.     new HtmlWebpackPlugin({ 
  15.       template: path.resolve(__dirname, "src""index.html"
  16.     }) 
  17.   ] 
  18. }; 

注意loader的出现顺序:首先是sass-loader,然后是css-loader,最后是style-loader。

现在,运行npm start,你应该会在HTML的头部看到加载的样式表:

 

打包现代 JavaScrip

webpack 本身并不知道如何转换JavaScript代码。 该任务已外包给babel的第三方 loader,特别是babel-loader。

babel是一个JavaScript编译器和“编译器”。babel 可以将现代JS(es6, es7...)转换为可以在(几乎)任何浏览器中运行的兼容代码。

同样,要使用它,我们需要安装一些 Loader:

  • babel-core :把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理
  • babel-preset-env:将现代 JS 编译为ES5
  • **babel-loader **:用于 webpack

引入依赖关系

  1. npm i @babel/core babel-loader @babel/preset-env --save-dev 

接着,创建一个新文件babel.config.json配置babel,内容如下:

  1.   "presets": [ 
  2.     "@babel/preset-env" 
  3.   ] 

最后在配置一下 webpack :

  1. const HtmlWebpackPlugin = require("html-webpack-plugin"); 
  2. const path = require("path"); 
  3.  
  4. module.exports = { 
  5.   module: { 
  6.     rules: [ 
  7.       { 
  8.         test: /\.scss$/, 
  9.         use: ["style-loader""css-loader""sass-loader"
  10.       }, 
  11.       { 
  12.         test: /\.js$/, 
  13.         exclude: /node_modules/, 
  14.         use: ["babel-loader"
  15.       } 
  16.     ] 
  17.   }, 
  18.   plugins: [ 
  19.     new HtmlWebpackPlugin({ 
  20.       template: path.resolve(__dirname, "src""index.html"
  21.     }) 
  22.   ] 
  23. }; 

要测试转换,可以在 src/index.js中编写一些现代语法:

  1. import "./style.scss"
  2. console.log("Hello webpack!"); 
  3.  
  4. const fancyFunc = () => { 
  5.   return [1, 2]; 
  6. }; 
  7.  
  8. const [a, b] = fancyFunc(); 

现在运行npm run dev来查看dist中转换后的代码。打开 dist/main.js并搜索“fancyFunc”:

  1. \n\nvar fancyFunc = function fancyFunc() {\n  return [1, 2];\n};\n\nvar _fancyFunc = fancyFunc(),\n    _fancyFunc2 = _slicedToArray(_fancyFunc, 2),\n    a = _fancyFunc2[0],\n    b = _fancyFunc2[1];\n\n//# sourceURL=webpack:///./src/index.js?" 

没有babel,代码将不会被转译:

  1. \n\nconsole.log(\"Hello webpack!\");\n\nconst fancyFunc = () => {\n  return [1, 2];\n};\n\nconst [a, b] = fancyFunc();\n\n\n//# sourceURL=webpack:///./src/index.js?");  

**注意:**即使没有babel,webpack也可以正常工作。仅在执行 ES5 代码时才需要进行代码转换过程。

在 Webpack 中使用 JS 的模块

webpack 将整个文件视为模块。但是,请不要忘记它的主要目的:加载ES模块。

ECMAScript模块(简称ES模块)是一种JavaScript代码重用的机制,于2015年推出,一经推出就受到前端开发者的喜爱。在2015之年,JavaScript 还没有一个代码重用的标准机制。多年来,人们对这方面的规范进行了很多尝试,导致现在有多种模块化的方式。

你可能听说过AMD模块,UMD,或CommonJS,这些没有孰优孰劣。最后,在ECMAScript 2015中,ES 模块出现了。

我们现在有了一个“正式的”模块系统。

要在 webpack 使用 ES module ,首先创建 src/common/usersAPI.js 文件:

  1. const ENDPOINT = "https://jsonplaceholder.typicode.com/users/"
  2.  
  3. export function getUsers() { 
  4.   return fetch(ENDPOINT) 
  5.     .then(response => { 
  6.       if (!response.ok) throw Error(response.statusText); 
  7.       return response.json(); 
  8.     }) 
  9.     .then(json => json); 

在 src/index.js中,引入上面的模块:

  1. import { getUsers } from "./common/usersAPI"
  2. import "./style.scss"
  3. console.log("Hello webpack!"); 
  4.  
  5. getUsers().then(json => console.log(json)); 

生产方式

如前所述,webpack有两种操作模式:开发(development )和(production)。到目前为止,我们仅在开发模式下工作。

在开发模式中,为了便于代码调试方便我们快速定位错误,不会压缩混淆源代码。相反,在生产模式下,webpac k进行了许多优化:

  • 使用 TerserWebpackPlugin 进行缩小以减小 bundle 的大小
  • 使用ModuleConcatenationPlugin提升作用域

在生产模式下配 置webpack,请打开 package.json 并添加一个“ build” 命令:

现在运行 npm run build,webpack 会生成一个压缩的包。

Code splitting

**代码拆分(Code splitting)**是指针对以下方面的优化技术:

  • 避免出现一个很大的 bundle
  • 避免重复的依赖关系

webpack 社区考虑到应用程序的初始 bundle 的最大大小有一个限制:200KB。

在 webpack 中有三种激活 code splitting 的主要方法:

  • 有多个入口点
  • 使用 optimization.splitChunks 选项
  • 动态导入

第一种基于多个入口点的技术适用于较小的项目,但是从长远来看它是不可扩展的。这里我们只关注第二和第三种方式。

Code splitting 与 optimization.splitChunks

考虑一个使用Moment.js 的 JS 应用程序,Moment.js是流行的时间和日期JS库。

在项目文件夹中安装该库:

  1. npm i moment 

现在清除src/index.js的内容,并引入 moment 库:

  1. import moment from "moment"

运行 npm run build 并查看控制的输出内容:

  1. main.js    350 KiB       0  [emitted]  [big]  main 

整个 moment 库都绑定到了 main.js 中这样是不好的。借助optimization.splitChunks,我们可以从主包中移出moment.js。

要使用它,需要在 webpack.config.js 添加 optimization 选项:

  1. const HtmlWebpackPlugin = require("html-webpack-plugin"); 
  2. const path = require("path"); 
  3.  
  4. module.exports = { 
  5.   module: { 
  6.   // ... 
  7.   }, 
  8.   optimization: { 
  9.     splitChunks: { chunks: "all" } 
  10.   }, 
  11.   // ... 
  12. }; 

运行npm run build 并查看运行结果:

  1. main.js   5.05 KiB       0  [emitted]         main 
  2. main.js    346 KiB       1  [emitted]  [big]  vendors~main 

现在,我们有了一个带有moment.js 的vendors〜main.js,而主入口点的大小更合理。

注意:即使进行代码拆分,moment.js仍然是一个体积较大的库。有更好的选择,如使用luxon或date-fns。

## Code splitting 与 动态导入

Code splitting的一种更强大的技术使用动态导入来有条件地加载代码。在ECMAScript 2020中提供此功能之前,webpack 提供了动态导入。

这种方法在 Vue 和 React 之类的现代前端库中得到了广泛使用(React有其自己的方式,但是概念是相同的)。

Code splitting 可用于:

  • 模块级别
  • 路由级别

例如,你可以有条件地加载一些 JavaScript 模块,以响应用户的交互(例如单击或鼠标移动)。或者,可以在响应路由更改时加载代码的相关部分。

要使用动态导入,我们先清除src/index.html,并写入下面的内容:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>Dynamic imports</title> 
  6. </head> 
  7. <body> 
  8. <button id="btn">Load!</button> 
  9. </body> 
  10. </html> 

在 src/common/usersAPI.js中:

  1. const ENDPOINT = "https://jsonplaceholder.typicode.com/users/"
  2.  
  3. export function getUsers() { 
  4.   return fetch(ENDPOINT) 
  5.     .then(response => { 
  6.       if (!response.ok) throw Error(response.statusText); 
  7.       return response.json(); 
  8.     }) 
  9.     .then(json => json); 

在 src/index.js 中

  1. const btn = document.getElementById("btn"); 
  2.  
  3. btn.addEventListener("click", () => { 
  4.   // 
  5. }); 

如果运行npm run start查看并单击界面中的按钮,什么也不会发生。

现在想象一下,我们想在某人单击按钮后加载用户列表。“原生”的方法可以使用静态导入从src/common /usersAPI.js加载函数:

  1. import { getUsers } from "./common/usersAPI"
  2.  
  3. const btn = document.getElementById("btn"); 
  4.  
  5. btn.addEventListener("click", () => { 
  6.   getUsers().then(json => console.log(json)); 
  7. }); 

问题在于ES模块是静态的,这意味着我们无法在运行时更改导入的内容。

通过动态导入,我们可以选择何时加载代码

  1. const getUserModule = () => import("./common/usersAPI"); 
  2.  
  3. const btn = document.getElementById("btn"); 
  4.  
  5. btn.addEventListener("click", () => { 
  6.   getUserModule().then(({ getUsers }) => { 
  7.     getUsers().then(json => console.log(json)); 
  8.   }); 
  9. }); 

这里我们创建一个函数来动态加载模块

  1. const getUserModule = () => import("./common/usersAPI"); 

现在,当你第一次使用npm run start加载页面时,会看到控制台中已加载 js 包:

 

现在,仅在单击按钮时才加载/common/usersAPI:

 

对应的 chunk 是 0.js

通过在导入路径前面加上魔法注释/ * webpackChunkName:“ name_here” * /,可以更改块名称:

  1. const getUserModule = () => 
  2.   import(/* webpackChunkName: "usersAPI" */ "./common/usersAPI"); 
  3.  
  4. const btn = document.getElementById("btn"); 
  5.  
  6. btn.addEventListener("click", () => { 
  7.   getUserModule().then(({ getUsers }) => { 
  8.     getUsers().then(json => console.log(json)); 
  9.   }); 
  10. }); 

 

作者:Valentino Gagliardi 译者:前端小智 来源:valentinog 原文:https://www.sitepoint.com/webpack-beginner-guide/

本文转载自微信公众号「大迁世界」,可以通过以下二维码关注。转载本文请联系大迁世界公众号。

 

责任编辑:武晓燕 来源: 大迁世界
相关推荐

2020-09-18 09:02:20

JavaScript

2024-01-12 14:37:29

智能家居人工智能

2022-04-24 15:21:01

MarkdownHTML

2020-12-15 14:05:15

云计算

2022-07-22 13:14:57

TypeScript指南

2010-06-13 11:13:38

UML初学者指南

2022-10-10 15:28:45

负载均衡

2021-05-10 08:50:32

网络管理网络网络性能

2022-03-28 09:52:42

JavaScript语言

2023-07-03 15:05:07

预测分析大数据

2023-07-28 07:31:52

JavaScriptasyncawait

2022-09-05 15:36:39

Linux日志记录syslogd

2018-10-28 16:14:55

Reactreact.js前端

2010-08-26 15:47:09

vsftpd安装

2012-03-14 10:56:23

web app

2023-02-10 08:37:28

2018-05-14 08:53:51

Linux命令shuf

2021-05-06 09:00:00

JavaScript静态代码开发

2014-04-01 10:20:00

开源Rails

2020-08-16 13:10:46

TensorFlow深度学习数据集
点赞
收藏

51CTO技术栈公众号