ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] # 简介 中文官网:https://webpack.docschina.org/ 中文社区:https://github.com/webpack-china/webpack.js.org Webpack 始于 2012 年,由 Tobias Koppers 发起,用于解决当时现有工具未解决的的一个难题:**构建复杂的单页应用程序 (SPA)。** # webpack 4 基础 webpack 4 中零配置的概念适用于: * `entry` point(入口点) 默认为 `./src/index.js ` * `output`(输出) 默认为 `./dist/main.js` * production (生产) 和 development (开发) 模式 (无需为生产和开发环境创建2个单独的配置) 这就够了。 但是对于在 webpack 4 中使用 loader (加载器),您仍然需要创建配置文件。 1. 默认打包后的文件是被压缩的 2. 新建配置文件(进行手动配置,**默认配置文件**名为:`webpack.config.js` 或者 `webpackfile.js`) ``` let path=require('path') module.exports = { mode:'development', //代表为开发模式 也可以是生产模式 production(打包后的文件为压缩模式) entry:'./src/index.js', //入口文件 output:{ filename:'bundle.js', //打包输出的文件名 path:path.resolve(__dirname,'dist') // 打包输出的路径(必须是绝对路径) } } ``` 3. 自定义配置文件名 ``` npx webpack --config webpack.config.custom.js ``` # WebPack 是什么 1、一个打包工具 2、一个模块加载工具 3、各种资源都可以当成模块来处理 4、网站 [http://webpack.github.io/](https://link.jianshu.com?t=http%3A%2F%2Fwebpack.github.io%2F) 如今,越来越多的JavaScript代码被使用在页面上,我们添加很多的内容在浏览器里。如何去很好的组织这些代码,成为了一个必须要解决的难题。 对于模块的组织,通常有如下几种方法:     1、通过书写在不同文件中,使用 script 标签进行加载     2、CommonJS进行加载(NodeJS 就使用这种方式)     3、AMD 进行加载(require.js 使用这种方式)     4、ES6 模块 > **思考:为什么只有****JS****需要被模块化管理,前端的很多预编译内容,不需要管理吗?** ![](https://upload-images.jianshu.io/upload_images/1419656-91e4422875a988ff.png?imageMogr2/auto-orient/strip|imageView2/2/w/1190/format/webp) 基于以上的思考,WebPack 项目有如下几个目标:     • 将依赖树拆分,保证按需加载     • 保证初始加载的速度     • 所有静态资源可以被模块化     • 可以整合第三方的库和模块     • 可以构造大系统 ## WebPack的特点 1、丰富的插件,方便进行开发工作 2、大量的加载器,包括加载各种静态资源 3、代码分割,提供按需加载的能力 4、发布工具 ## WebPack的优势      • webpack 是以 commonJS 的形式来书写脚本滴,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。 • 能被模块化的不仅仅是 JS 了。 • 开发便捷,能替代部分 grunt/gulp 的工作,比如打包、压缩混淆、图片转 base64 等。 • 扩展性强,插件机制完善,特别是支持 React 热插拔(见 react-hot-loader )的功能让人眼前一亮。 ## 模块化打包工具 webpack是一种模块化的工具,每个资源对于webpack来讲都是一个模块,模块之间的引用,它们之间存在的关系,webpack都可以处理好。     1、兼容多种JS模块规范     2、更好地打包静态资源     3、更好地处理模块间的关系 > [引用 Node 模块和 NPM 模块](https://webpack.toobug.net/zh-cn/chapter2/umd.html) # 深入了解 webpack ## 流程细节 Webpack 的构建流程可以分为以下三大阶段: 1. 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。 2. 编译:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。 3. 输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。 Webpack 打包,最基本的实现方式,是将所有的模块代码放到一个对象(或数组)里,通过属性名(数组ID)对应的路径查找模块,如下所示,可以发现入口`index.js`的代码是放在对应路径值中,其它 `a.js` 和 `b.js` 的代码分别放在了属性名为路径名的对应值中,而 webpack 引用的时候,主要通过`__webpack_require__`的方法引用不同索引的模块。 ``` /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function // 定义了一个 require 函数 /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { // installedModules 模块缓存 /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = "./src/index.js"); // 启动程序 /******/ }) /************************************************************************/ /******/ ({ /***/ "./src/a.js": /*!******************!*\ !*** ./src/a.js ***! \******************/ /*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b */ \"./src/b.js\");\n\n\n\n\nconsole.log(\"b file value: \"+ _b__WEBPACK_IMPORTED_MODULE_0__[\"value\"]);\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ('a'+ _b__WEBPACK_IMPORTED_MODULE_0__[\"value\"]);\n\n//# sourceURL=webpack:///./src/a.js?"); /***/ }), /***/ "./src/b.js": /*!******************!*\ !*** ./src/b.js ***! \******************/ /*! exports provided: value */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"value\", function() { return value; });\n\n\nconst value = 'b';\n\n//# sourceURL=webpack:///./src/b.js?"); /***/ }), /***/ "./src/index.js": /*!**********************!*\ !*** ./src/index.js ***! \**********************/ /*! no exports provided */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a */ \"./src/a.js\");\n\n\nconsole.log(\"Hello Webpack! \" + _a__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\n\n//# sourceURL=webpack:///./src/index.js?"); /***/ }) /******/ }); ``` ## 与Grunt、Gulp、browserify的区别 * Grunt/Gulp 1. Grunt/Gulp 工具链、构建工具,可以配合各种插件做js压缩,css压缩,less编译 替代手工实现自动化工作 2. 自动化 3. 提高效率用 4. 可以配置 seajs、requirejs 甚至 webpack的插件。   * webpack 模块化打包 1. webpack 是一种模块化打包工具; 2. 能够将css、js、image打包为一个JS文件; 3. 更智能的模块打包; 4. 更丰富的插件、模块加载器。 * seajs / require 是一种在线"编译" 模块的方案,相当于在页面上加载一个 CMD/AMD 解释器。这样浏览器就认识了 define、exports、module 这些东西。也就实现了模块化。 * browserify 是一个预编译模块的方案,相比于上面 ,这个方案更加智能。 ## gulp 结合 Webpack ~~~ import gulpWebpack from 'webpack-stream' ~~~ [https://github.com/shama/webpack-stream](https://github.com/shama/webpack-stream) ## WebPack的配置 每个项目下都可能会有 webpack 配置或是其他形式(如 create-react-app),用来告诉 webpack 它需要做什么。 下面是一个例子: ``` const webpack = require('webpack'); const commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); module.exports = {     //插件项     plugins:[commonsPlugin],     //页面入口文件配置     entry:{         index : './src/index.js'     },     //入口文件输出配置     output:{         path: 'dist/js/page',         filename: '[name].js'     },     module:{         // 加载器配置         loaders:[ { test: /\.eot$|\.svg$|\.ttf$|\.woff$/, // 字体图标最终会以 base64 的方式被打包到 CSS 中 use: [ { loader: 'url-loader' } ] }, { test: /\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, { loader: 'less-loader', options: { noIeCompat: true } } ] }, { test: /\.js$/, include, exclude, use: "babel-loader", }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader' }         ]     },     // 其它解决方案配置     resolve:{         extensions: [ '.js', '.json', '.scss'],         alias:{             AppStore : 'js/stores/AppStores.js',             ActionType : 'js/actions/ActionType.js',             AppAction : 'js/actions/AppAction.js'         }     } }; ``` 1. `plugins` 是插件项,这里我们使用了一个 `CommonsChunkPlugin` 的插件,它用于提取多个入口文件的公共脚本部分,然后生成一个 `common.js` 来方便多页面之间进行复用。 2. `entry` 是页面入口文件配置,`output` 是对应输出项配置 (即入口文件最终要生成什么名字的文件、存放到哪里) 3. `module.loaders` 是最关键的一块配置。它告知 webpack 每一种文件都需要使用什么加载器来处理。 **所有加载器需要使用 npm 加载** 4. 最后是 `resolve` 配置,配置查找模块的路径和扩展名和别名(方便书写) ## Loaders 加载器 webpack 本身只能解析`.js`和`.json`文件,loader 是将其他类型转成**模块**, 以添加到依赖图中。它接收两个属性,分别是`test`和`use`, 前者是一个**正则表达式**, 匹配需要被 loader 转换的文件格式,后者是**使用的 loader 名**. ``` module.exports = { //... module: { rules: [ { test: /\.js$/, exclude: /node_modules/, // 不解析 node_modules include: path.resolve('src'), // 只解析 src 目录下的文件,两者写其一即可 use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'] } } } ] } } ``` 对于 loader, 还有两种使用用方式,分别是**内联**和**CLI**, 不过都不常用. ``` // 内联 loader import Styles from 'style-loader!css-loader?modules!./styles.css'; // CLI loader webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader' ``` ## webpack 常用命令: ``` webpack        # 最基本的启动webpack命令 webpack -w  # 提供watch方法,实时进行打包更新 webpack -p   # 对打包后的文件进行压缩 webpack -d   # 提供SourceMaps,方便调试 webpack --config pathToConfigFile # 使用 --config 参数传入路径 webpack --colors # 输出结果带彩色,比如:会用红色显示耗时较长的步骤 webpack --profile # 输出性能数据,可以看到每一步的耗时 webpack --display-modules # 默认情况下node\_modules下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块 ``` ## 入口文件配置:entry 参数 entry 可以是字符串(单入口),可以是数组(多入口),但为了后续发展,请务必使用 object,因为 这个 object 中的 key 在 webpack 里相当于此入口的 name,既可以后续用来拼生成文件的路径,也可以用来作为此入口的唯一标识。我推荐的形式是这样的: ``` entry: {    // pagesDir 是前面准备好的入口文件集合目录的路径   'alert/index':path.resolve(pagesDir, `./alert/index/page`),   'index/login':path.resolve(pagesDir, `./index/login/page`),   'index/index':path.resolve(pagesDir, `./index/index/page`), }, ``` 对照我的脚手架项目 [webpack-seed](https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2FArray-Huang%2Fwebpack-seed) 的文件目录结构,就很清楚了: ``` ├─src # 当前项目的源码    ├─pages # 各个页面独有的部分,如入口文件、只有该页面使用到的 css、模板文件等    │  ├─alert # 业务模块    │  │  └─index # 具体页面    │  ├─index # 业务模块    │  │  ├─index # 具体页面    │  │  └─login # 具体页面 ``` 由于每一个入口文件都相当于 entry 里的一项,因此这样一项一项地来写实在是有点繁琐,我就稍微写了点代码来拼接这 entry: ``` var pageArr = [     'index/login',     'index/index',     'alert/index', ]; var configEntry = {}; pageArr.forEach((page) => {    configEntry[page] = path.resolve(pagesDir, page +'/page'); }); ``` ## 输出文件:`output`参数  `output` 参数告诉 webpack 以什么方式来生成/输出文件,值得注意的是,与`entry` 不同,`output`相当于一套规则,所有的入口都必须使用这一套规则,不能针对某一个特定的入口来制定 output 规则。`output`参数里有这几个子参数是比较常用的:path、publicPath、filename、chunkFilename,这里先给个 [webpack-seed](https://github.com/Array-Huang/webpack-seed) 中的示例: ``` output: {      path: buildDir, // var buildDir = path.resolve(__dirname, './build');      publicPath: '../../../../build/',      filename: '[name]/entry.js',    // [name] 表示 entry 每一项中的 key,用以批量指定生成后文件的名称      chunkFilename: '[id].bundle.js', }, ``` ### `path` path 参数表示生成文件的根目录,需要传入一个**绝对路径**。 path 参数和后面的 filename 参数共同组成入口文件的完整路径。 ### `publicPath` `publicPath`参数表示的是一个 URL 路径(指向生成文件的根目录),用于生成 css / js /图片/字体文件等资源的路径,以确保网页能正确地加载到这些资源。  publicPath 参数跟 path 参数的区别是:path 参数其实是针对本地文件系统的,而 publicPath 则针对的是浏览器;因此,publicPath 既可以是一个相对路径,如示例中的`../../../../build/,`也可以是一个绝对路径如`http://www.xxxxx.com/`。 一般来说,我还是更推荐相对路径的写法,这样的话整体迁移起来非常方便。那什么时候用绝对路径呢? 其实也很简单,当你的 html 文件跟其它资源放在不同的域名下的时候,就应该用绝对路径了,这种情况非常多见于 CDN 和后端渲染模板的场景。 ### `filename` filename 属性表示的是如何命名生成出来的入口文件,规则有以下三种: 1. `[name]`:指代入口文件的`name`,也就是上面提到的`entry`参数的 `key`,因此,我们可以在`name`里利用 `/`,即可达到控制文件目录结构的效果。 2. `[hash]`:指代**本次编译**的一个`hash`版本(an hash of the compilation. See the [Caching guide](https://devdocs.io/webpack/guides/caching) for details.),值得注意的是,只要是在同一次编译过程中生成的文件,这个 `[hash]` 的值就是一样的;在缓存的层面来说,相当于一次全量的替换。 3. `[chunkhash]`:指代的是当前 chunk 的一个 hash 版本,也就是说,在同一次编译中,每一个 chunk 的 hash 都是不一样的;而在两次编译中,如果某个 chunk 根本没有发生变化,那么该 chunk 的 hash 也就不会发生变化。这在缓存的层面上来说,就是把缓存的粒度精细到具体某个 chunk,只要 chunk 不变,该 chunk 的浏览器缓存就可以继续使用。 下面来说说如何利用 `filename` 参数和 `path` 参数来设计入口文件的目录结构,如示例中的 `path:buildDir, // var buildDir = path.resolve(__dirname, './build');` 和 `filename: '[name]/entry.js'`, 那么对于`key`为 `'index/login'` 的入口文件,生成出来的路径就是 `build/index/login/entry.js` 了,怎么样,是不是很简单呢? ### chunkFilename `chunkFilename`参数与 `filename` 参数类似,都是用来定义生成文件的命名方式的,只不过,`chunkFilename`参数指定的是除入口文件外的`chunk`(这些`chunk`通常是由于 webpack 对代码的优化所形成的,比如因应实际运行的情况来异步加载)的命名。 # chunk(分片) webpack也提供了代码分片机制,使我们能够将代码拆分后进行异步加载。 > 值得注意的是,webpack 对代码拆分的定位仅仅是为了解决文件过大,无法并发加载,加载时间过长等问题,并不包括公共代码提取和复用的功能。对公共代码的提取将由`CommonChunks`插件来完成。 要使用webpack的分片功能,首先需要定义“分割点”,即代码从哪里分割成两个文件。具体的方式有两种: `require.ensure`和 AMD `require`两种方式,来建立分割点,代码在此处被分片。 ~~~ var a=require('./a'); a.sayHello(); require.ensure(['./b'], function(require){ var b = require('./b'); b.sayHello(); }); require(['./c'], function(c){ c.sayHello(); }); ~~~ 打包后的代码: * bundle.js -> main.js + a.js * 1.bundle.js -> b.js * 2.bundle.js -> c.js > [第三章 webpack进阶-分片](https://webpack.toobug.net/zh-cn/chapter3/chunks.html ## 各种Loader配置:module 参数 webpack的核心实际上也只能针对 js 进行打包,那 webpack 一直号称能够打包任何资源是怎么一回事呢? 原来,webpack拥有一个类似于插件的机制,名为Loader,通过 Loader,webpack 能够针对每一种特定的资源做出相应的处理。Loader 的种类相当多,有些比较基础的是官方自己开发,而其它则是由 webpack 社区开源贡献出来的,这里是 Loader 的List:[list of loaders](https://webpack.docschina.org/loaders/)。 而 module 正是配置什么资源使用哪个 Loader的参数(因为就算是同一种资源,也可能有不同的 Loader 可以使用,当然不同 Loader 处理的手段不一样,最后结果也自然就不一样了)。 ### `module.rules` 配置 loaders参数又有几个子参数,先给出一个官方示例: ``` module: { rules: [ {    // "test" is commonly used to match the file extension    test:/\.jsx$/,    // "include" is commonly used to match the directories    include: [      path.resolve(__dirname,"app/src"),      path.resolve(__dirname,"app/test")    ],     // "exclude" should be used to exclude exceptions     // try to prefer "include" when possible     // the "loader"     loader:"babel-loader" } ] } ``` 下面一一对这些子参数进行说明: 1. test:用来指示当前配置项针对哪些资源,该值应是一个条件值(condition)。 2. exclude:用来剔除掉需要忽略的资源,该值应是一个条件值(condition)。 3. include:用来表示本 loader 配置仅针对哪些目录/文件,该值应是一个条件值(condition)。这个参数跟test参数的效果是一样的(官方文档也是这么写的),我也不明白为嘛有俩同样规则的参数,不过我们姑且可以自己来划分这两者的用途:test 参数用来指示文件名(包括文件后缀),而 include 参数则用来指示目录;注意同时使用这两者的时候,实际上是 and 的关系。 4. loader/use:`ule.loader`是`Rule.use: [ { loader } ]`的简写。详细请查看[`Rule.use`](https://webpack.docschina.org/configuration/module/#rule-use)和[`UseEntry.loader`](https://webpack.docschina.org/configuration/module/#useentry)。 需要注意的是,loader 是可以接受参数的,方式类似于 URL 参数,形如`css?minimize&-autoprefixer`,具体每个loader 接受什么参数请参考 loader 本身的文档(一般也就只能在 github 里看了)。 ## 添加额外功能:plugins参数 这 plugins 参数相当于一个插槽位(类型是数组),你可以先按某个 plugin 要求的方式初始化好了以后,把初始化后的实例丢到这里来。 ## 图片打包细则 [webpack4 配置 (3)- 打包 css/js/ 图片等资源](https://juejin.im/post/5cb3fa77e51d456e8c1d3c21) [超详细使用 webpack4.x 搭建标准前端项目](https://zhuanlan.zhihu.com/p/76689742) ## 集成 jQuery 在本项目中比较特殊,因为对于第三方类库统一采用了 dll 单独打包方式,但由于 jQuery 不支持 CDM,所以打包采用 extenal 的形式打包的。 1. 直接引入 我们最常用的引入方式,就是用 AMD 或者 ES6 模块导入的形式在具体的业务模块中直接引入: ``` // header.js import $ from 'jquery'; // 或者 const $ = require('jquery'); $('h1').hide(); ``` 如果 webpack 配置文件没有做其他相关设置,那么在这种情况下,jQuery 源码会和业务代码最终会打包到一个文件中。 倘若多个业务模块都引用了 jQuery,则在打包时,webpack 很机智,不会对 jQuery 源码进行多次打包。 即**最终打包的文件,只包含一份 jQuery 源码**。 但在业务代码中需要反复使用`import $ from 'jquery'`来引入 jQuery 2. Webpack 的 ProvidePlugin 插件: ``` // jQuery引用插件 var jQueryProvidePlugin = new webpack.ProvidePlugin({     $: 'jQuery',     jQuery: 'jQuery',     'window.jQuery':'jQuery',     'window.$':'jQuery' }); ``` 然后在我们任意源码中: ~~~ // in a module $('#item'); // <= 起作用 jQuery('#item'); // <= 起作用 // $ 自动被设置为 "jquery" 输出的内容 ~~~ 3. expose-loader 先把 jQuery对象声明成为全局变量`jQuery`,再通过管道进一步又声明成为全局变量`$`。 ~~~ require("expose-loader?$!jquery"); // loader: 'expose?$!expose?jQuery' ~~~ 或者,你可以通过配置文件来设置: ~~~ // webpack.config.js module: { rules: [{ test: require.resolve('jquery'), use: [{ loader: 'expose-loader', options: 'jQuery' },{ loader: 'expose-loader', options: '$' }] }] } ~~~ [`require.resolve`](https://nodejs.org/api/modules.html#modules_require_resolve_request_options) 调用是一个 Node.js 函数 (与 webpack 处理流程中的`require.resolve`无关)。`require.resolve`用来获取模块的绝对路径。 4. `externals` 防止被打包 针对第三方库(如 jQuery),我们一般用相对路径或者**类似 CDN 这种**绝对路径的形式,以`<script>`标签在页面里直接引入。这里我们拿 CDN 上的 jQuery 做演示: ~~~ <!-- index.html --> ... <script src="https://code.jquery.com/jquery-3.1.0.js"></script> ~~~ 最后,无需在业务代码中引入第三方库,就能直接使用 jQuery 的 API: ~~~ // header.js $('h1').hide(); ~~~ > [webpack externals 深入理解](https://segmentfault.com/a/1190000012113011) > [webpack 分离第三方库及公用文件](https://yi-jy.com/2018/06/09/webpack-split-chunks/) # 开发技巧 > [【Hybrid 开发高级系列】WebPack 模块化专题](https://www.jianshu.com/p/c915685b5c88) ## 多入口配置 多入口的项目 最好是自动获取目录下的入口文件: ``` ... const setMPA = () => { const entry = {}; const htmlWebpackPlugins = []; const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js')); Object.keys(entryFiles) .map((index) => { const entryFile = entryFiles[index]; // '/Users/cpselvis/my-project/src/index/index.js' const match = entryFile.match(/src\/(.*)\/index\.js/); const pageName = match && match[1]; entry[pageName] = entryFile; htmlWebpackPlugins.push( new HtmlWebpackPlugin({ template: path.join(__dirname, `src/${pageName}/index.html`), filename: `${pageName}.html`, chunks: [pageName], inject: true, minify: { html5: true, collapseWhitespace: true, preserveLineBreaks: false, minifyCSS: true, minifyJS: true, removeComments: false } }) ); }); return { entry, htmlWebpackPlugins } } const { entry, htmlWebpackPlugins } = setMPA(); module.exports = { ... ``` 其实本质就是如下: ``` ... entry:{ 'index':'./src/index/index.js', // 页面一的 js 'one':'./src/one/index.js', // 页面二的 js }, // 输入路径 output: { path: path.join(__dirname,'../dist'), // 输出的路径 filename: "[name].build.js", // 分别输出不同的 js,【name】是跟上面的 entry 对应 }, plugins:[ new htmlwebpackplugin({ filename: 'index.html', template: 'src/index/index.html', chunks: ['index'], // 选项的作用主要是针对多入口(entry)文件。当你有多个入口文件的时候,对应就会生成多个编译后的 js 文件。 // 那么 chunks 选项就可以决定是否都使用这些生成的 js 文件。 // chunks 默认会在生成的 html 文件中引用所有的 js 文件,当然你也可以指定引入哪些特定的文件。 inject: true, hash: true }), new htmlwebpackplugin({ filename: 'one.html', template: 'src/one/index.html', chunks: ['one'], inject: true, hash: true }), ], ... ``` > [《玩转 webpack》极客时间课程源码和课件](https://github.com/cpselvis/geektime-webpack-course) # 命令构建输出 示例: ~~~ Hash: aafe36ba210b0fbb7073 Version: webpack 4.1.1 Time: 338ms Built at: 3/16/2018 3:40:14 PM Asset Size Chunks Chunk Names main.js 679 bytes 0 [emitted] main index.html 181 bytes [emitted] Entrypoint main = main.js [0] ./src/index.js + 1 modules 219 bytes {0} [built] | ./src/index.js 77 bytes [built] | ./src/component.js 142 bytes [built] Child html-webpack-plugin for "index.html": 1 asset Entrypoint undefined = index.html [0] (webpack)/buildin/module.js 519 bytes {0} [built] [1] (webpack)/buildin/global.js 509 bytes {0} [built] + 2 hidden modules ~~~ 输出告诉了我们许多: * `Hash: aafe36ba210b0fbb7073`\- 构建生成的唯一 hash 标志。 你可以`[hash]`来验证静态资源(assets)是否有效。hash 的使用将在[*在文件名中添加 hash*](https://lvzhenbang.github.io/webpack-book/dist/zh/optimizing/04_adding_hashes_to_filenames.html)这章详解。 * `Version: webpack 4.1.1`\- Webpack 的版本。 * `Time: 338ms`\- 构建完成所花费的时间。 * `main.js 679 bytes 0 [emitted] main`\- 生成的静态资源名称、大小、相关联模块的 ID、状态、模块名字。 * `index.html 181 bytes [emitted]`\- 构建过程中生成的另一个静态资源。 * `[0] ./src/index.js + 1 modules 219 bytes {0} [built]`\- 入口静态资源的 ID、名字、大小、入口 ID、生成方式。 * `Child html-webpack-plugin for "index.html":`\- 输出使用的插件。 # 整体配置结构 ~~~ const webpack= require('webpack'); const path = require('path'); const TerserPlugin = require('terser-webpack-plugin'); // 混淆压缩 js module.exports = { mode: 'production', // (默认值),https://webpack.docschina.org/concepts/mode // entry 表示 入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。 // 类型可以是 string | object | array entry: './app/entry', // 只有1个入口,入口只有1个文件 entry: ['./app/entry1', './app/entry2'], // 只有1个入口,入口有2个文件 entry: { // 有2个入口 a: './app/entry-a', b: ['./app/entry-b1', './app/entry-b2'] }, // 如何输出结果:在 Webpack 经过一系列处理后,如何输出最终想要的代码。 output: { // 输出文件存放的目录,必须是 string 类型的绝对路径。 path: path.resolve(__dirname, 'dist'), // 输出文件的名称 filename: 'bundle.js', // 完整的名称 filename: '[name].js', // 当配置了多个 entry 时,通过名称模版为不同的 entry 生成不同的文件名称 filename: '[chunkhash].js', // 根据文件内容 hash 值生成文件名称,用于浏览器长时间缓存文件 // 发布到线上的所有资源的 URL 前缀,string 类型 publicPath: '/assets/', // 放到指定目录下 publicPath: '', // 放到根目录下 publicPath: 'https://cdn.example.com/', // 放到 CDN 上去 // 导出库的名称,string 类型 // 不填它时,默认输出格式是匿名的立即执行函数 library: 'MyLibrary', // 导出库的类型,枚举类型,默认是 var // 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp , libraryTarget: 'umd', // 是否包含有用的文件路径信息到生成的代码里去,boolean 类型 pathinfo: true, // 附加 Chunk 的文件名称 chunkFilename: '[id].js', chunkFilename: '[chunkhash].js', // JSONP 异步加载资源时的回调函数名称,需要和服务端搭配使用 jsonpFunction: 'myWebpackJsonp', // 生成的 Source Map 文件名称 sourceMapFilename: '[file].map', // 浏览器开发者工具里显示的源码模块名称 devtoolModuleFilenameTemplate: 'webpack:///[resource-path]', // 异步加载跨域的资源时使用的方式 crossOriginLoading: 'use-credentials', crossOriginLoading: 'anonymous', crossOriginLoading: false, }, // 配置模块相关 module: { rules: [ // 配置 Loader { test: /\.jsx?$/, // 正则匹配命中要使用 Loader 的文件 exclude: [ // 不解析这里面的文件 path.resolve(__dirname, 'app/demo-files') ], include: [ // 只解析这目录下的文件,两者写其一即可 path.resolve(__dirname, 'app') ], use: [ // 使用那些 Loader,有先后次序,从后往前执行 'style-loader', // 直接使用 Loader 的名称 { loader: 'css-loader', options: { // 给 css-loader 传一些参数 } } ] }, ], // 防止解析那些任何与给定正则表达式相匹配的文件,忽略的文件中不应该含有任何导入机制。忽略大型的`library`可以提高构建性能。 // 使用正则表达式:noParse: /jquery|lodash/ // 使用函数,从 Webpack 3.0.0 开始支持 noParse: (content)=> { // content 代表一个模块的文件路径 // 返回 true or false return /jquery|lodash/.test(content); } noParse: /jquery|lodash/ }, // 配置插件 plugins: [ new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // 使用`IgnorePlugin`在打包时忽略本地化内容 ], // 配置寻找模块的规则 resolve: { modules: [ // 寻找模块的根目录,array 类型,默认以 node_modules 为根目录 'node_modules', path.resolve(__dirname, 'app') ], extensions: ['.js', '.json', '.jsx', '.css'], // 模块的后缀名 alias: { // 模块别名配置,用于映射模块 // 把 'module' 映射 'new-module',同样的 'module/path/file' 也会被映射成 'new-module/path/file' 'module': 'new-module', // 使用结尾符号 $ 后,把 'only-module' 映射成 'new-module', // 但是不像上面的,'module/path/file' 不会被映射成 'new-module/path/file' 'only-module$': 'new-module', }, alias: [ // alias 还支持使用数组来更详细的配置 { name: 'module', // 老的模块 alias: 'new-module', // 新的模块 // 是否是只映射模块,如果是 true 只有 'module' 会被映射,如果是 false 'module/inner/path' 也会被映射 onlyModule: true, } ], symlinks: true, // 是否跟随文件软链接去搜寻模块的路径 descriptionFiles: ['package.json'], // 模块的描述文件 mainFields: ['main'], // 模块的描述文件里的描述入口的文件的字段名称 enforceExtension: false, // 是否强制导入语句必须要写明文件后缀 }, // 输出文件性能检查配置 performance: { hints: 'warning', // 有性能问题时输出警告 hints: 'error', // 有性能问题时输出错误 hints: false, // 关闭性能检查 maxAssetSize: 200000, // 最大文件大小 (单位 bytes) maxEntrypointSize: 400000, // 最大入口文件大小 (单位 bytes) assetFilter: function(assetFilename) { // 过滤要检查的文件 return assetFilename.endsWith('.css') || assetFilename.endsWith('.js'); } }, devtool: 'source-map', // 配置 source-map 类型 context: __dirname, // Webpack 使用的根目录,string 类型必须是绝对路径 // 配置输出代码的运行环境 target: 'web', // 浏览器,默认 target: 'webworker', // WebWorker target: 'node', // Node.js,使用 `require` 语句加载 Chunk 代码 target: 'async-node', // Node.js,异步加载 Chunk 代码 target: 'node-webkit', // nw.js target: 'electron-main', // electron-主线程 target: 'electron-renderer', // electron-渲染线程 externals: { // 使用来自 JavaScript 运行环境提供的全局变量 jquery: 'jQuery' }, stats: { // 控制台输出日志控制 assets: true, colors: true, errors: true, errorDetails: true, hash: true, }, devServer: { // DevServer 相关的配置 proxy: { // 代理到后端服务接口 '/api': 'http://localhost:3000' }, contentBase: path.join(__dirname, 'public'), // 配置 DevServer HTTP 服务器的文件根目录 compress: true, // 是否开启 gzip 压缩 historyApiFallback: true, // 是否开发 HTML5 History API 网页 hot: true, // 是否开启模块热替换功能 hotOnly: true // 如果模块热替换功能不生效,则不刷新网页 https: false, // 是否开启 HTTPS 模式 }, profile: true, // 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳 cache: false, // 是否启用缓存提升构建速度 watch: true, // 是否开始 watchOptions: { // 监听模式选项 // 不监听的文件或文件夹,支持正则匹配。默认为空 ignored: /node_modules/, // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高 // 默认为300ms aggregateTimeout: 300, // 判断文件是否发生变化是不停的去询问系统指定文件有没有变化,默认每秒问 1000 次 poll: 1000 }, //===优化 optimization=== optimization: { minimizer: [ new TerserPlugin({ cache: true, parallel: true, sourceMap: true, terserOptions: { // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions } }) ] }, // 默认配置 splitChunks: { chunks: 'async', // all 全部(推荐), async 分割异步块, initial minSize: 30000, // 抽取出来的文件在压缩前的最小大小 maxSize: 0, // 抽取出来的文件在压缩前的最大大小, 默认为 0,表示不限制最大大小 minChunks: 1, // 最小公用模块次数 maxAsyncRequests: 5, // 按需加载时并行请求的最大数量 maxInitialRequests: 3, // 入口点的最大并行请求数 automaticNameDelimiter: '~', // 文件名称分隔符号 // 文件名,值可以是 boolean | function (module, chunks, cacheGroupKey) | string name: true, // 缓存策略,默认设置了分割 node_modules 和公用模块 // 会继承splitChunks的配置,但是test、priorty和reuseExistingChunk只能用于配置缓存组 cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 // 优先级 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true // 是否复用存在的 chunk } } } } ~~~ > [时下最流行前端构建工具Webpack 入门总结](https://mp.weixin.qq.com/s/vdOnXCUGWv6oEEIaKOXFuQ) # Webpack tricks [使用Webpack的技巧和窍门](https://github.com/rstacruz/webpack-tricks) # 参考 [如何使用Webpack创建JavaScript library](https://github.com/iuap-design/blog/issues/323) https://x-team.com/blog/rollup-webpack-parcel-comparison/ [Webpack的由来](https://survivejs.com/webpack/foreword/) http://webpack.wuhaolin.cn/