# 前言 在前端工程化越来越复杂的今天,模块打包工具在我们的开发中起到了越来越重要的作用,其中webpack就是最热门的打包工具之一。 说到webpack,可能很多小伙伴会觉得既熟悉又陌生,熟悉是因为几乎在每一个项目中我们都会用上它,又因为webpack复杂的配置和五花八门的功能感到陌生。尤其当我们使用诸如[vuecli](https://cli.vuejs.org/zh/)之类的开发工具 还帮我们把webpack配置再封装一层的时候,webpack的本质似乎离我们更加遥远和深不可测了。 那么当我们的项目进行到一定程度,不可避免的需要接触webpack的配置和使用,本篇将从基础到原理讲解,来打开webpack的大门。 # 认识Webpack ### webpack是什么? 从官网上的描述我们其实不难理解,webpack 是一种前端资源构建工具,一个静态模块打包器。 * 前端资源是哪些资源?:这些前端资源就是浏览器不认识的 web 资源, 比如 sass、less、ts,包括 js 里的高级语法(ES6+)。这些资源要能够在浏览器中正常工作,必须一一经过编译处理。而 webpack 就是可以集成这些编译工具的一个总的构建工具。 * 静态模块打包器:静态模块就是 web 开发过程中的各种资源文件,webpack 根据引用关系,构建一个依赖关系图,然后利用这个关系图将所有静态模块打包成一个或多个 bundle(包,类似一捆一捆的稻草)输出。 ### 为什么我们需要 Webpack? 回答这个问题,可以和还没有 Webpack、没有构建工具时对比一下,就能明显地感觉出来了。这里就来列举一下不使用构建工具时的痛点。 * 跨域代理:web 开发时调用后端接口跨域,需要其他工具代理或者其他方式规避。 * 开发体验:改动代码后要手动刷新浏览器,如果做了缓存还需要清缓存刷新。 * 编译兼容:因为 js 和 css 的兼容性问题,很多新语法学习了却不能使用,无论是开发效率和个人成长都受影响。 * 模块打包:打包问题。需要使用额外的平台如 jenkins 打包,自己编写打包脚本,对各个环节如压缩图片,打包 js、打包 css 都要一一处理。 ...... 而这些问题,Webpack 都提供了解决方案,你只需要做一些简单的配置就可以上手使用了。当然,Webpack 做的还不止这些,下面就来一一介绍。 # 使用webpack 首先我们需要理解webpack的几个核心概念。它们分别是: * entry: 入口,指示 Webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。 * output: 输出,指示 Webpack 打包后的资源 bundles 输出到哪里,以及如何命名。 * loader: 模块转换器,用于把模块原内容按照需求转换成新内容。通俗的来讲Webpack 自身只能理解 JavaScript 和 json 文件,loader 让 Webpack 能够处理其他文件(vue,less,jsx...)。 * plugins: 扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情。 书写解释,下面简单介绍下上诉配置如何书写。 ##### 入口(entry): | ``` // string方式: 单入口,打包形成一个chunk,输出一个buldle文件。chunk的名称默认是main.js entry: "./src/index.js", // array方式:多入口,所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件 entry: ["./src/index.js", "./src/test.js"], // object:多入口,有几个入口文件就形成几个chunk,输出几个bundle文件。此时chunk的名称就是对象key值 entry:{ index:"./src/index.js", test:"./src/test.js", } ``` | | | -- | #### 输出(output): | ``` output: { // 输出文件目录(将来所有资源输出的公共目录,包括css和静态文件等等) path: path.resolve(__dirname, "dist"), //默认 // 入口文件名称(指定名称+目录) filename: "[name].js", // 默认 // 所有资源引入公共路径前缀,一般用于生产环境,小心使用 (例如,你最终编译出来的代码部署不是在根目录下,例如:https://www.xxxx.com/my-app/ 那么 publicPath需要设置为 ‘/my-app/’) publicPath: "", /* 非入口文件chunk的名称。所谓非入口即import动态导入形成的chunk或者optimization中的splitChunks提取的公共chunk 它支持和 filename 一致的内置变量 */ chunkFilename: "[contenthash:10].chunk.js", clean: true, // 打包前清空输出目录,相当于clean-webpack-plugin插件的作用,webpack5新增。 /* 当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到library */ library: { name: "[name]",//整个库向外暴露的变量名 type: "window"//库暴露的方式 } }, ``` | | | -- | #### 加载器(Loader): | ``` rules: [ { // 匹配哪些文件 test: /\.css$/, // 使用哪些loader进行处理。执行顺序,从右至左,从下至上 use: [ // 创建style标签,将js中的样式资源(就是css-loader转化成的字符串)拿过来,添加到页面head标签生效 "style-loader", // 将css文件变成commonjs一个模块加载到js中,里面的内容是样式字符串 "css-loader", { // css 兼容处理 postcss,注意需要在package.json配置browserslist loader: "postcss-loader", options: { postcssOptions: { ident: "postcss", // postcss-preset-env插件:帮postcss找到package.json中的browserslist配置,根据配置加载指定的兼容性样式 plugins: [require("postcss-preset-env")()], }, }, }, ], }, { test: /\.js$/, // 注意需要在package.json配置browserslist,否则babel-loader不生效 // js兼容处理 babel loader: "babel-loader", // 规则只使用一个loader时推荐写法 options: { presets: [ [ "@babel/preset-env",// 预设:指示babel做怎么样的兼容处理 { useBuiltIns: "usage", //按需加载 corejs: { version: "3", }, targets: "defaults", } ] ] } }, ], ``` | | | -- | #### 插件(plugins): | ``` / CleanWebpackPlugin帮助你在打包时自动清除dist文件,学习时使用比较方便 // const { CleanWebpackPlugin } = require("clean-webpack-plugin"); //从webpack5开始,webpack内置了该功能,只要在ouput中配置clear为true即可 // HtmlWebpackPlugin帮助你创建html文件,并自动引入打包输出的bundles文件。支持html压缩。 const HtmlWebpackPlugin = require("html-webpack-plugin"); // 该插件将CSS提取到单独的文件中。它会为每个chunk创造一个css文件。需配合loader一起使用 const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 该插件将在Webpack构建过程中搜索CSS资源,并优化\最小化CSS const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin"); // vue-loader V15版本以上,需要引入VueLoaderPlugin插件,它的作用是将你定义过的js、css等规则应用到vue文件中去。(处理SFCs) const { VueLoaderPlugin } = require('vue-loader') module.exports = { module: { rules: [ { test: /\.vue$/, loader: "vue-loader" }, { test: /\.css$/, use: [ // MiniCssExtractPlugin.loader的作用就是把css-loader处理好的样式资源(js文件内),单独提取出来 成为css样式文件 MiniCssExtractPlugin.loader,//生产环境下使用,开发环境还是推荐使用style-loader "css-loader", ], }, ], }, plugins: [ new HtmlWebpackPlugin({ template:"index.html" }), new MiniCssExtractPlugin({ filename: "css/built.css", }), new OptimizeCssAssetsWebpackPlugin(), new VueLoaderPlugin(), ] } ``` | | | -- | # 初始化一个项目 新建一个文件夹,命名为webpack-demo。推荐大家参考文档等,一步一步学习配置,不要直接找网上的最佳实践等等。你自己亲自配置的符合你项目的,就是最佳实践。 使用 npm init -y 进行初始化(也可以使用 yarn)。 要使用 webpack,那么必然需要安装 webpack、webpack-cli: | ``` pnpm install webpack webpack-cli -D ``` | | | -- | 鉴于前端技术变更迅速,列出本篇文章基于 webpack 的版本号: ├── webpack 5.66.0 └── webpack-cli 4.9.1 从 wepack V4.0.0 开始, webpack 是开箱即用的,在不引入任何配置文件的情况下就可以使用。 新建 src/index.js 文件,我们在文件中随便写点什么: | ``` //index.js class Animal { constructor(name) { this.name = name; } getName() { return this.name; } } const dog = new Animal('dog'); ``` | | | -- | 使用 [npx](https://www.ruanyifeng.com/blog/2019/02/npx.html) webpack --mode=development 进行构建,默认是 production 模式,我们为了更清楚得查看打包后的代码,使用 development 模式。 可以看到项目下多了个 dist 目录,里面有一个打包出来的文件 main.js。 webpack 有默认的配置,如默认的入口文件是 ./src,默认打包到dist/main.js。更多的默认配置可以查看: node_modules/webpack/lib/WebpackOptionsDefaulter.js。 查看 dist/main.js 文件,可以看到,src/index.js 并没有被转义为低版本的代码,这显然不是我们想要的。 | ``` /* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). */ /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./src/index.js": /*!**********************!*\ !*** ./src/index.js ***! \**********************/ /***/ (() => { eval("class Animal {\r\n constructor(name) {\r\n this.name = name\r\n }\r\n getName() {\r\n return this.name\r\n }\r\n}\r\n\r\nconst dog = new Animal('dog')\r\n\n\n//# sourceURL=webpack://webpack-demo/./src/index.js?"); /***/ }) /******/ }); /************************************************************************/ /******/ /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module can't be inlined because the eval devtool is used. /******/ var __webpack_exports__ = {}; /******/ __webpack_modules__["./src/index.js"](); /******/ /******/ })() ; ``` | | | -- | # 将JS转义为低版本 前面我们说了 webpack 的四个核心概念,其中之一就是 loader,loader 用于对源代码进行转换,这正是我们现在所需要的。 将JS代码向低版本转换,我们需要使用 [babel-loader](https://github.com/babel/babel-loader)。 首先安装一下 babel-loader | ``` pnpm install babel-loader -D ``` | | | -- | 此外,我们还需要配置 babel,为此我们安装一下以下依赖: | ``` pnpm install @babel/core @babel/preset-env @babel/plugin-transform-runtime -D pnpm install @babel/runtime @babel/runtime-corejs3 ``` | | | -- | 如何配置Babel,请参考:[带你一步一步配置Babel7](https://juejin.cn/post/6844904132294213639) and [不容错过的 Babel7 知识](https://juejin.cn/post/6844904008679686152) 新建 webpack.config.js,内容如下: | ``` module.exports = { module: { rules: [ { test: /\.js$/, use: ['babel-loader'], exclude: /node_modules/ //排除 node_modules 目录 } ] } } ``` | | | -- | 建议给 loader 指定 include 或是 exclude,指定其中一个即可,因为 node_modules 目录通常不需要我们去编译,排除后,有效提升编译效率。 这里,我们可以在 .babelrc 中编写 babel 的配置,也可以在 webpack.config.js 中进行配置。 #### 创建一个 .babelrc 配置如下: | ``` { "presets": ["@babel/preset-env"], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] } ``` | | | -- | 现在,我们重新执行 npx webpack --mode=development,查看 dist/main.js,会发现已经被编译成了低版本的JS代码。 小伙伴们可以发现,我在执行webpack打包的时候,运行的一直都是npx webpack --mode=development,是不是可以将mode作为一个配置项,配置在webpack.config.js中呢?显然是可以的。 #### 将 mode 增加到 webpack.config.js 中: | ``` module.exports = { // .... mode: "development", // ... } ``` | | | -- | mode 配置项,支持以下三个配置: | 选项 | 描述 | | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | -------------------------------------------------------------------------------------------------------------------------------------------------- | | | development | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。(打包更加快速,省了代码优化步骤) | | - | - | | production | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。(打包比较慢,会开启 tree-shaking 和 压缩代码) | | none | 不使用任何默认优化选项 | 现在,我们直接使用 npx webpack 进行编译即可。 # 在浏览器中查看页面 搞了这么久,还不能在浏览器中查看页面,这显然不能忍! 查看页面,难免就需要 html 文件,有小伙伴可能知道,有时我们会指定打包文件中带有 hash,那么每次生成的 js 文件名会有所不同,总不能让我们每次都人工去修改 html,这样不是显得我们很蠢嘛~ 我们可以使用 html-webpack-plugin 插件来帮助我们完成这些事情。 首先,安装一下插件: | ``` pnpm install html-webpack-plugin -D ``` | | | -- | 新建 public 目录,并在其中新建一个 index.html 文件( 文件内容使用 html:5 快捷生成即可) 修改 webpack.config.js 文件。 | ``` //首先引入插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { //... plugins: [ // 数组 放着所有的webpack插件 new HtmlWebpackPlugin({ template: './public/index.html', filename: 'index.html', //打包后的文件名 minify: { removeAttributeQuotes: false, //是否删除属性的双引号 collapseWhitespace: false, //是否折叠空白 }, // hash: true //是否加上hash,默认是 false }) ] } ``` | | | -- | 此时执行 npx webpack,可以看到 dist 目录下新增了 index.html 文件,并且其中自动插入了 发表评论 取消回复 使用cookie技术保留您的个人信息以便您下次快速评论,继续评论表示您已同意该条款 评论 * 私密评论 名称 * 🎲 邮箱 地址 发表评论 提交中...