初识 Webpack

429 阅读6分钟

一、为什么要学 Webpack?

当前 Web 开发面临的困境 文件依赖关系错综复杂、静态资源请求效率低、模块化支持不友好、浏览器对高级 JS 特性兼容性较低

Webpack 是一个流行的前端项目构建工具(打包工具),可以解决当前 web 开发中所面临的困境,是⼯程化、⾃动化思想在前端开发中的体现。 webpack 提供了友好的模块化支持,以及代码压缩混淆、处理 js 兼容问题(需要结合babel)、性能优化等强大的功能,从而让程序员把 工作的重心放到具体的功能实现上,提高了开发效率和项目的可维护性。。

二、 环境搭建

  1. 新建项目空白目录,并运行 npm init –y 命令,初始化包管理配置文件 package.json
  2. 运行 npm install webpack webpack-cli –D 命令,安装 webpack 相关的包
    1. -D 开发依赖, --save-dev 简写,只是为了帮助你更好的开发,项目运行不会依赖的文件,不会被部署到线上
    2. -S 运行依赖, --save 简写
    3. webpack4+,都需要安装webpack-cli,用来以 webpack 协议连接相应服务
    4. npx webpack -v 检查是否安装成功 npx(npx 的原理很简单,就是运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检查命令是否存在。由于 npx 会检查环境变量$PATH,所以系统命令也可以调用。)

三、启动 webpack 执⾏构建

1. module chunk bundle 的区别

  • module - 各个源码文件, webpack 中一切皆模块
  • chunk - 代码块 可以是多模块合并成的
  • bundle - 最终的输出文件

2.默认配置

  • webpack默认⽀持JS模块和JSON模块
  • ⽀持CommonJS Es moudule AMD等模块类型
  • webpack4⽀持零配置使⽤,但是很弱,稍微复杂些的场景都需要额外扩展

3. 执行构建

  1. 新建 src 源代码目录
  2. 新建 src -> index.js 目录结构
  3. 执行构建
  • 第一种方式: npx webpack
  • 第二种方式:修改 package.json
"scripts": {
 "test": "webpack"
}

npm run test

  1. 构建成功后我们会发现⽬录下多出⼀个dist⽬录,⾥⾯有个main.js,这个 ⽂件是⼀个可执⾏的JavaScript⽂件,⾥⾯包含webpackBootstrap 启动函数。

四、Webpack 的核心概念

webpack有默认的配置⽂件,webpack.config.js,我们可以 对这个⽂件进⾏修改,进⾏个性化配置

webpack.config.js配置基础结构

module.exports = {
   entry: "./src/index.js", //打包⼊⼝⽂件
   output: "./dist", //输出结构
   mode: "production", //打包环境
   module: {
     rules: [
       //loader模块处理
       {
         test: /\.css$/,
         use: "style-loader"
       }
     ]
   },
   plugins: [new HtmlWebpackPlugin()] //插件配置
};

1. entry

入口(entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。

entry 的值可以是 字符串,数组,对象

1.1 字符串

//单⼊⼝ SPA,本质是个字符串
entry:{
 main: './src/index.js'
}
等同于
entry: "./src/index.js",

1.2 数组

entry: ["./src/index.js", "./src/other.js"]
  • webpack 会自动生成另外一个入口模块,并将数组中的每个指定的模块加载进来,并将最后一个 module.exports 作为入口模块的 module.exports 导出。

1.3 对象

//多⼊⼝ entry是个对象
entry: {
        // 默认值
        // main: "./src/index.js",
        index: "./src/index.js",
        other: "./src/other.js"
   	}

注意:只有对象这种方式才是多入口的

2. output

输出(output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。

output: {
   filename: "bundle.js",//输出⽂件的名称
   path: path.resolve(__dirname, "dist")//输出⽂件到磁盘的⽬录,必须是绝对路径
   },

//多⼊⼝的处理
output: {
   filename: "[name][chunkhash:8].js",//利⽤占位符,⽂件名称不要重复
   path: path.resolve(__dirname, "dist")//输出⽂件到磁盘的⽬录,必须是绝对路径
}

2.1 占位符

  • hash
    • 每次构建会生成一个hash。和整个项目有关,只要有项目文件更改,就会改变hash
  • chunkhash
    • 根据不同的 entry 进行依赖解析,构建对应的 chunk, 生成相应的hash 只要组成 entry 的模块没有内容改动,则对应的 hash 不变
  • contenthash
    • 和单个文件的内容相关。指定文件的内容发生改变,就会改变hash
  • name
  • id 理解webpack的hash,contenthash,chunkhash

3. mode

模式(mode)指示 webpack 使用相应模式的配置:

描述特点
development会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。能让代码本地调试 运行的环境
production会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin。能让代码优化上线 运行的环境
none退出任何默认优化选项

development: 的开启会有利于热更新的处理,识别哪个模块变化。 production: 自动开启代码压缩, Tree-Shaking, Vue React等会删除掉调式代码(如开发环境的 warning), 处理副作⽤等⼀些功能

4. loader

loader 让 webpack 能 够 去 处 理 那 些 不识别的文件 (webpack 自 身 只 理 解 .js 和 .json)

常见的loader

style-loader
css-loader
less-loader
sass-loader
postcss-loader 添加浏览器前缀
ts-loader //将Ts转换成js
babel-loader//转换ES6、7等js新特性语法
file-loader //处理静态资源模块
url-loader // file-loader加强版本, 还可以把图片转成 base64 的格式
eslint-loader
...

5. module

模块,在 Webpack ⾥⼀切皆模块,⼀个模块对应着⼀个⽂件。 Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

当webpack处理到不认识的模块时,需要在webpack中的module 处进⾏配置,当检测到是什么格式的模块,使⽤什么loader来处 理。

5.1 处理样式

注意: loader 执行顺序从后往前

module.epxort = {
	module: {
            rules: [
                {
                    test: /\.less$/, // 指定匹配规则, 参数正则表达式
                // loader 模块转换 模块处理
                // loader 的执行顺序是从后往前
                // css-loader 是把 css 模块的内容 加入到 js 模块中去 
                // css in js 方式
                // style-loader 从 js 中提取 css 的 loader 在 html 中创建 style 标签 把 css 的内容放在这个 style 标签中
                    use: [
                        'style-loader',
                        'css-loader',
                        'less-loader'
                    ]
                }
            ]
        }
}

样式⾃动添加前缀

Postcss-loader

npx install postcss-loader autoprefixer -D

新建 postcss.config.js, postcss-loader 的配置文件

// postcss.config.js
module.exports = {
    plugins: [
        require("autoprefixer")({
            overrideBrowserslist: ["last 2 versions",">1%"]
        })
    ]
};
// webpack.config.js
{
    test: /\.css$/,
    use: ["style-loader", "css-loader", "postcss-loader"]
}

5.2 处理图片、字体等静态资源文件

  • file-loader:处理静态资源模块

原理是把打包⼊⼝中识别出的资源模块,移动到输出⽬录,并且 返回⼀个地址名称

场景:就是当我们需要模块,仅仅是从源代码挪移到打包⽬录, 就可以使⽤file-loader来处理,txt,svg,csv,excel,图⽚资 源啦等等

yarn add file-loader -D
 {
        test: /\.(png|jpe?g|gif)$/,
        //use使⽤⼀个loader可以⽤对象,字符串,两个loader需要⽤数组
        use: {
            loader: "file-loader",
            // options额外的配置,⽐如资源名称
            options: {
            // placeholder 占位符 [name]⽼资源模块的名称
            // [ext]⽼资源模块的后缀
            // name: "[name]_[hash].[ext]",
            //打包后的存放位置
            outputPath: "images/"
            }
        }
    }
{
   test: /\.(eot|ttf|woff|woff2|svg)$/,
   use: "file-loader"
}
  • url-loader: file-loader加强版本, file-loader 能干它都能干,另外它还可以将图片转换成 base64 格式,适用于小图片base64的利弊
yarn add url-loader -D
{
    test: /\.(png|jpe?g|gif)$/,
    use: {
        loader: "url-loader",
        options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            // ⼩于2048,才转换成base64
            limit: 2048 // 单位字节 1024 = 1kb
        }
    }
}

6. plugins

插件(plugins)作⽤于webpack打包整个过程, webpack的打包过程是有(⽣命周期概念)钩⼦,plugin 可以在webpack运⾏到某个阶段的时候,帮你做⼀些事情,类似于⽣命周期的概念, 可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等。

6.1 HtmlWebpackPlugin

htmlwebpackplugin会在打包结束后,⾃动⽣成⼀个html⽂件,并 把打包⽣成的js模块引⼊到该html中。

npm install -D html-webpack-plugin

配置:

title: ⽤来⽣成⻚⾯的 title 元素

filename: 输出的 HTML ⽂件名,默认是 index.html, 也可以直
接配置带有⼦⽬录。

template: 模板⽂件路径,⽀持加载器,⽐如 html!./index.html

inject: true | 'head' | 'body' | false ,注⼊所有的资
源到特定的 template 或者 templateContent 中,如果设置为
true 或者 body,所有的 javascript 资源将被放置到 body 元
素的底部,'head' 将放置到 head 元素中。

favicon: 添加特定的 favicon 路径到输出的 HTML ⽂件中。

minify: {} | false , 传递 html-minifier 选项给 minify
输出

hash: true | false, 如果为 true, 将添加⼀个唯⼀的
webpack 编译 hash 到所有包含的脚本和 CSS ⽂件,对于解除
cache 很有⽤。

cache: true | false,如果为 true, 这是默认值,仅仅在⽂件
修改之后才会发布⽂件。

showErrors: true | false, 如果为 true, 这是默认值,错误
信息会写⼊到 HTML ⻚⾯中

chunks: 允许只添加某些块 (⽐如,仅仅 unit test 块)

chunksSortMode: 允许控制块在添加到⻚⾯之前的排序⽅式,⽀持
的值:'none' | 'default' | {function}-default:'auto'

excludeChunks: 允许跳过某些块,(⽐如,跳过单元测试的块)

示例:

const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
    plugins: [
        new htmlWebpackPlugin({
            title: "My App",
            filename: "app.html",
            template: "./src/index.html"
        })
    ]
}
<!-- index.html 模板 -->
<!DOCTYPE html>
<html lang="en">
   <head>
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=devicewidth, initial-scale=1.0" />
   <meta http-equiv="X-UA-Compatible"
  content="ie=edge" />
  	<!-- 使用 htmlWebpackPlugin 里的标题 -->
   	<title><%= htmlWebpackPlugin.options.title %>
  </title>
   </head>
   <body>
   	<div id="root"></div>
   </body>
</html>

6.2 clean-webpack-plugin

帮我们清除 打包之后 dist 目录下的其他多余或者无用的代码

npm install clean-webpack-plugin -D
const { CleanWebpackPlugin } = require("cleanwebpack-plugin");

module.epxorts = {
	plugins: [
 		new CleanWebpackPlugin()
	]
}

6.3 mini-css-extract-plugin

将CSS提取到单独的文件中。它为每个包含CSS的JS文件创建一个CSS文件。它支持CSS和SourceMap的按需加载。

yarn add mini-css-extract-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
	module: {
          rules: [
              {
                 test: /\.css$/,
                 use: [MiniCssExtractPlugin.loader, "css-loader"]
              }
          ]
   	},
    plugins: [
 		new MiniCssExtractPlugin({
       		filename: "[name][contenthash:8].css"
        })
    ]
 
}

五、Babel

1. Babel 处理 ES6

Babel是JavaScript编译器,能将ES6代码转换成ES5代码,让我们开发过程中放⼼使⽤JS新特性⽽不⽤担⼼兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。

Babel在执⾏编译的过程中,会从项⽬根⽬录下的 .babelrc JSON⽂件中读取配置。没有该⽂件会从 loader的options地⽅读取配置。

npm i babel-loader @babel/core @babel/preset-env -D

1.babel-loader是webpack 与 babel的通信桥梁,不会做把es6转成es5的⼯作,这部分⼯作需要⽤到 @babel/preset-env来做 2.@babel/preset-env⾥包含了es,6,7,8转es5的转换规则 Babel在执⾏编译的过程中,会从项⽬根⽬录下的 .babelrc JSON⽂件中读取配置。没有该⽂件会从 loader的options地⽅读取配置。

{
   test: /\.js$/,
   exclude: /node_modules/,
   use: {
   	loader: "babel-loader",
   	options: {
   		presets: ["@babel/preset-env"]
   	}
   } 
}
// .babelrc
{
    "presets": [
        [
            "babel/preset-env"
        ]
    ],
    "plugins": [

    ]
}

通过上⾯的⼏步 还不够,默认的Babel只⽀持let等⼀些基础的特性转换,Promise等⼀些符合 ES5 语法规范得 ES6 新出的 API 还没有转换过来,这时候需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性

2. babel-polyfill

  • Polyfill 是很早提出的概念,就是根据浏览器兼容性进行兼容
  • regenerator(支持 generator(生成器函数)语法) 和 core-js(其他 ES6 语法)
  • babel-polyfill 即两者的集合 (Babel 7.4 弃用),推荐直接使用core-js 和 regenerator

安装

npm install --save @babel/polyfill

注意: 以全局变量的⽅式注⼊进来的。windows.Promise,它会造成全局对象的污染

// 打包入口文件 index.js 顶部
import "@babel/polyfill";

按需加载, 减少冗余 打包入口文件头部不需要引入 "@babel/polyfill"

// .babelrc
{
    "presets": [
        [
            "babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": 3
            }
        ]
    ],
    "plugins": []
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4f47d0d14d342cd8fba1f4dea1a3a77~tplv-k3u1fbpfcp-watermark.image)}

useBuiltIns 选项是 babel 7 的新功能,这个选项告诉 babel 如何配置 @babel/polyfill

作用
entry需要在 webpack 的⼊⼝⽂件⾥ import "@babel/polyfill" ⼀次。 babel 会根据你的使⽤情况导⼊垫⽚,没有使⽤的功能不会被导⼊相应的垫⽚。
usage不需要 import ,全⾃动检测,但是要安装 @babel/polyfill 。
false如果你 import "@babel/polyfill" ,它不会排除掉没有使⽤的垫⽚,程序体积会庞⼤。(不推荐)

3. babel-runtime

将有兼容性 API 更改成另一个名称,从而不污染全局变量

例如:Promise 变成 _Promise

npm i @babel/runtime -S
npm i @babel/plugin-transform-runtime -D
// .babelrc
{
    "presets": [
        [
            "babel/preset-env"
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "absoluteRuntime": false,
                "corejs": 3,
                "helpers": true,
                "regenerator": true,
                "useESModules": false
            }
        ]
    ]
}