【webpack】指南

官网指南地址

管理资源&管理输出

管理资源

webpack 最出色的功能之一就是,除了 JavaScript还可以通过 loader 引入任何其他类型的文件webpack.config.js配置如下:

 const path = require('path');
 module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
        {
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.(png|svg|jpg|gif)$/,
            use: [
                'file-loader'
            ]
        },
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/,
            use: [
                'file-loader'
            ]
        },
        {
            test: /\.(csv|tsv)$/,
            use: [
               'csv-loader'
            ]
        },
        {
            test: /\.xml$/,
            use: [
               'xml-loader'
            ]
        }
      ]
    }
};

管理输出

配置HtmlWebpackPlugin插件自动生成index.html;配置CleanWebpackPlugin插件自动清理dist目录WebpackManifestPlugin插件可以提取manifestwebpack.config.js配置如下:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    entry: {
        app: './src/index.js',
        print: './src/print.js'
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
        title: 'Output Management'
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};

开发环境&生产环境

  • 开发环境(development)和生产环境(production)的构建目标差异很大。
  • 在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source maplocalhost server
  • 而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。
  • 由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。
  • 但是,我们会遵循不重复原则,保留一个“通用”配置。我们将使用一个名为 webpack-merge 的工具合并配置
npm install --save-dev webpack-merge
image

开发模式&模块热替换

image

生产环境

  1. 代码压缩:UglifyJSPlugin或其他工具
  2. source map用还是不用?
  3. 许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容
new webpack.DefinePlugin({
   'process.env.NODE_ENV': JSON.stringify('production')
})
  1. ExtractTextPlugin:将 CSS 分离成单独的文件
  2. CLI 替代选项:原来用的一些插件现在可以替换成一些优化配置项:例如,--optimize-minimize 标记将在后台引用 UglifyJSPlugin。和以上描述的 DefinePlugin 实例相同,--define process.env.NODE_ENV="'production'" 也会做同样的事情。并且,webpack -p 将自动地调用上述这些标记,从而调用需要引入的插件。

构建性能

  1. 保持版本最新webpack/node/npm
  2. 尽量少使用不同的工具loader/plugins;必要的话也用在尽量最少数的必要模块中;可以将非常消耗资源的 loaders 转存到 worker pool
//使用 include 字段仅将 loader 模块应用在实际需要用其转换的位置中:
{
  test: /\.js$/,
  include: path.resolve(__dirname, "src"),
  loader: "babel-loader"
}
  1. Dlls:使用 DllPlugin更改不频繁的代码进行单独编译。这将改善引用程序的编译速度,即使它增加了构建过程的复杂性。
  2. Smaller = Faster:减少编译的整体大小,以提高构建性能。尽量保持 chunks 小巧。
  3. 使用 cache-loader 启用持久化缓存。使用 package.json 中的 postinstall 清除缓存目录。thread-loader 可以将非常消耗资源的 loaders 转存到 worker pool
  4. 提高解析速度:
    • 尽量减少 resolve.modules, resolve.extensions, resolve.mainFiles, resolve.descriptionFiles 中类目的数量,因为他们会增加文件系统调用的次数。
    • 如果你不使用 symlinks ,可以设置 resolve.symlinks: false
    • 如果你使用自定义解析 plugins ,并且没有指定 context 信息,可以设置 resolve.cacheWithContext: false
  5. 开发环境中
    • 硬避免在生产环境下才会用到的工具:UglifyJsPlugin、ExtractTextPlugin、[hash]/[chunkhash]、AggressiveSplittingPlugin、AggressiveMergingPlugin、ModuleConcatenationPlugin
    • 不同的 devtool 的设置,会导致不同的性能差异
    • 在内存中进行代码的编译和资源的提供,但并不写入磁盘来提高性能:webpack-dev-server
    • 尽量减少入口 chunk 的体积,以提高性能
  6. 其他

tree shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.jsonsideEffects 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 pure(纯的 ES2015 模块)",由此可以安全地删除文件中未使用的部分。

  1. sideEffects:将文件标记为无副作用
    如同上面提到的,如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack,它可以安全地删除未用到的 export 导出。
{
  "name": "your-project",
  "sideEffects": false
}

如果你的代码确实有一些副作用,那么可以改为提供一个数组:

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js"
  ]
}
  1. 压缩输出:通过如上方式,我们可以,找出那些需要删除的“未使用代码”,然而,我们不只是要找出,还需要在 bundle 中删除它们。为此,我们将使用 -p(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。
    • 注意,--optimize-minimize 标记也会在 webpack 内部调用 UglifyJsPlugin。)
    • webpack 4 开始,也可以通过 mode 配置选项轻松切换到压缩输出,只需设置为 production
      image

懒加载(动态导入/按需加载)

vue单页应用中,当项目不断完善丰富时,即使使用webpack打包,文件依然是非常大的,影响页面的加载。如果我们能把不同路由对应的组件分割成不同的代码块,当路由被访问时才加载对应的组件(也就是按需加载),这样就更加高效了。——引自vue-router官方文档

非动态加载时的打包情况如下图:

image

如下案例中hello组件的加载方式改为路由懒加载[import()语法],在进行打包

// import Hello from '@/components/Hello'
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      // component: Hello,
      component: () => import('@/components/Hello')
    }
  ]
})
image
  • 很明显的看到,打包后有4个js文件,仔细的同学还发现,app.js文件的大小加上新多出文件的大小,约等于没有分割打包的app的大小。
  • 这样等于异步加载的组件,是单独打包成了一个js,在页面首次加载的时候不需要加载他,等到请求相应的页面的时候在去服务器请求它,减小了页面首屏加载的时间
  • webpack.prod.conf.js 中配置 output.chunkFilename 规定了打包异步文件的格式
//webpack.prod.conf.js:生产打包js
//utils.assetsPath是utils.js中封装的一个路径相关的方法
output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    /**
        本人在自己的工程中删除或修改chunkFilename的配置,
        最后还是都按这个规则生产js文件了,
        Why?
    */
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 
},
  • 不需要刻意懒加载。下面的案例中,代码确实会在脚本运行的时候产生一个分离的代码块 lodash.bundle.js,在技术概念上“懒加载”它。问题是加载这个包并不需要用户的交互 -- 意思是每次加载页面的时候都会请求它。这样做并没有对我们有很多帮助,还会对性能产生负面影响。
//src/index.js
- import _ from 'lodash';
-
- function component() {
+ function getComponent() {
-   var element = document.createElement('div');
-
-   // Lodash, now imported by this script
-   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
+     var element = document.createElement('div');
+
+     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+     return element;
+
+   }).catch(error => 'An error occurred while loading the component');
  }

- document.body.appendChild(component());
+ getComponent().then(component => {
+   document.body.appendChild(component);
+ })
  • 当页面有个按钮点击事件时,需要加载某个组件,这种用户交互的情景下,我们可以使用懒加载;当然,路由也是交互的一种。

缓存

客户端(通常是浏览器)获取资源是比较耗费时间的。可以通过命中缓存,以降低网络流量,使网站加载速度更快;然而,缓存的存在可能会使你获取新代码时比较棘手(文件名不变的话)。如何通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件

  • 通过使用 output.filename 进行文件名替换
    image
  • 如果我们不做修改,然后再次运行构建,我们以为文件名会保持不变。然而,如果我们真的运行,可能会发现情况并非如此(译注:如果不做修改,文件名可能会变,也可能不会。
  • 这也是因为 webpack 在入口 chunk 中,包含了某些样板(boilerplate),特别是 runtimemanifest译注:样板(boilerplate)指 webpack 运行时的引导代码
  • 即:如果 webpack 生成的 hash 发生改变,manifest 文件也会发生改变。因此,vendor bundle 的内容也会发生改变,并且失效。所以,我们需要将 manifest 文件提取出来。
  • CommonsChunkPluginmanifest提取出来
    image
  • webpack 里每个模块都有一个 module idmodule id 是该模块在模块依赖关系图里按顺序分配的序号,如果这个 module id 发生了变化,那么他的 chunkhash 也会发生变化。
  • 这样会导致:如果你引入一个新的模块,会导致module id 整体发生改变,可能会导致所有文件的chunkhash发生变化,这显然不是我们想要的
  • 这里需要用 HashedModuleIdsPlugin,根据模块的相对路径生成一个四位数的hash作为模块id,这样就算引入了新的模块,也不会影响 module id 的值,只要模块的路径不改变的话。
new webpack.HashedModuleIdsPlugin()

创建library

除了打包应用程序代码,webpack 还可以用于打包 JavaScript library;即我们平时npm install下来的那种依赖包

我的demo:element-ui表单的二次封装

image

最后,package.json中配置"main": "dist/ginna-form.js";我们从node_modules引入时就靠这个属性来找到对应的文件的哦。


shimming

webpack 编译器(compiler)能够识别遵循 ES2015 模块语法、CommonJSAMD 规范编写的模块。然而,一些第三方的库(library)可能会引用一些全局依赖(例如 jQuery 中的 $)。这些库也可能创建一些需要被导出的全局变量这些“不符合规范的模块”就是 shimming 发挥作用的地方

shim:一种库(library)的抽象,这种库能将一个新的 API 引入到一个旧的环境中,而且仅靠旧的环境中已有的手段实现。polyfill 就是一个用在浏览器 API 上的 shim

1. 让jQuery作为全局变量,可以被别的组件引用

image

2. 将模块中的this置为window

当模块运行在 CommonJS 环境下this会变成一个问题,也就是说此时的 this 指向的是 module.exports。在这个例子中,你可以通过使用 imports-loader 覆写 this

image

3. 某个库(library)创建出一个全局变量,它期望用户使用这个变量

  • 你可能从来没有在自己的源码中做过这些事情,但是你也许遇到过一个老旧的库(library),和下面所展示的代码类似。
  • 在下图用例中,我们可以使用exports-loader,将一个全局变量作为一个普通的模块来导出。例如,为了将 file 导出为 file 以及将 helpers.parse 导出为 parse
    image

4. babel-polyfill 按需加载

  • Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而可以在现有环境执行,所以我们可以用ES6编写,而不用考虑环境支持的问题。
  • Babel默认只转换新的JavaScript语法(syntax),如箭头函数等,而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码;因此我们需要polyfill
    image

5. 深度优化包babel-preset-env

  • babel-preset-env 是一个新的preset,可以根据配置的目标运行环境(environment)自动启用需要的 babel 插件
  • 之前我们写 javascript 代码时,需要使用 Npreset,比如:babel-preset-es2015、babel-preset-es2016es2015 可以把 ES6 代码编译为 ES5es2016可以把 ES2016 代码编译为 ES6babel-preset-latest 可以编译 stage 4 进度的 ECMAScript 代码。
  • 问题是我们几乎每个项目中都使用了非常多的preset,包括不必要的。
  • babel-preset-env 的工作方式类似 babel-preset-latest,唯一不同的就是它会根据配置的 env 只编译那些还不支持的特性。
  • 使用这个插件,你讲再也不需要使用 es20xx presets 了。

6. 其他工具

  • 还有一些其他的工具能够帮助我们处理这些老旧的模块。
  • script-loader 会在全局上下文中对代码进行取值,类似于通过一个 script 标签引入脚本。在这种模式下,每一个标准的库(library)都应该能正常运行
  • 这些老旧的模块如果没有 AMD/CommonJS 规范版本,但你也想将他们加入 dist 文件,你可以使用 noParse 来标识出这个模块
noParse:这是module中的一个属性,
作用:不去解析属性值代表的库的依赖

举例:
我们一般引用jquery,可以如下引用:
import jq from 'jquery'

对于上面的解析规则:
当解析jq的时候,会去解析jq这个库是否有依赖其他的包

我们对类似jq这类依赖库,一般会认为不会引用其他的包(特殊除外,自行判断)。
所以,对于这类不引用其他的包的库,我们在打包的时候就没有必要去解析,
这样能够增加打包速率。
所以,可以在webpack的配置中增加noParse属性
(以下代码只需要看module的noParse属性)

module.exports = {
    mode:'development',
    entry:'./src/index.js',
    output:{
        filename:'bundle.js',
        path:path.resolve(__dirname,'dist')
    },
    module:{
        noParse:/jquery/,//不去解析jquery中的依赖库
        rules:[ ]
    }
}

渐进式网络应用程序PWA

渐进式网络应用程序(Progressive Web Application - PWA),是一种可以提供类似于原生应用程序(native app)体验的网络应用程序(web app)。PWA 可以用来做很多事。其中最重要的是,在离线(offline)时应用程序能够继续运行功能。这是通过使用名为 Service Workers 的网络技术来实现的。

//print.js
export default function printMe() {
  console.log('I get called from print.js!');
}

//index.js
import printMe from './print.js';

if ('serviceWorker' in navigator) {
   window.addEventListener('load', () => {
     navigator.serviceWorker.register('./service-worker.js').then(registration => {
       console.log('SW registered: ', registration);
     }).catch(registrationError => {
       console.log('SW registration failed: ', registrationError);
     });
   });
}

function component() {
    var element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    var btn = document.createElement('button');
    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;
    element.appendChild(btn);
    return element;
}
document.body.appendChild(component());

//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    //3个插件都需要npm install --save-dev
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'Output Management'
            title: 'Progressive Web Application'
        }),
        //添加 Workbox
        new WorkboxPlugin.GenerateSW({
            // 这些选项帮助 ServiceWorkers 快速启用
            // 不允许遗留任何“旧的” ServiceWorkers
            clientsClaim: true,
            skipWaiting: true
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};

//package.json配置脚本
"scripts": {
    "build": "webpack",
    //使用一个简易服务器,搭建出我们所需的离线体验
    //npm install http-server --save-dev
    "start": "http-server dist"
  },
  • 有了 Workbox,我们再看下执行 npm run build 时会发生什么
clean-webpack-plugin: /mnt/c/Source/webpack-follow-along/dist has been removed.
Hash: 6588e31715d9be04be25
Version: webpack 3.10.0
Time: 782ms
                                                Asset       Size  Chunks                    Chunk Names
                                        app.bundle.js     545 kB    0, 1  [emitted]  [big]  app
                                      print.bundle.js    2.74 kB       1  [emitted]         print
                                           index.html  254 bytes          [emitted]
precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js  268 bytes          [emitted]
                                    service-worker.js       1 kB          [emitted]
   [0] ./src/print.js 87 bytes {0} {1} [built]
   [1] ./src/index.js 477 bytes {0} [built]
   [3] (webpack)/buildin/global.js 509 bytes {0} [built]
   [4] (webpack)/buildin/module.js 517 bytes {0} [built]
    + 1 hidden module
Child html-webpack-plugin for "index.html":
     1 asset
       [2] (webpack)/buildin/global.js 509 bytes {0} [built]
       [3] (webpack)/buildin/module.js 517 bytes {0} [built]
        + 2 hidden modules
  • 现在你可以看到,生成了 2 个额外的文件:service-worker.js(或sw.js) 和体积很大的 precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.jssw.jsService Worker 文件,precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.jssw.js 引用的文件,所以它也可以运行
  • 然后用 npm start 启动服务。访问 http://localhost:8080/index.html 并查看 console 控制台。在那里你应该看到:
SW registered
  • 现在来进行测试。停止服务器并刷新页面。如果浏览器能够支持 Service Worker,你应该可以看到你的应用程序还在正常运行。然而,服务器已经停止了服务,此刻是 Service Worker 在提供服务。

TypeScript

准备工作:ts-loader插件、tsconfig.json配置文件(和package.json同级)、ts文件中如何使用第三方库(ts声明文件*.d.ts

  1. webpack.config.js配置
    image
  2. tsconfig.json案例:
    image
  3. 使用第三方库
    当从 npm 安装第三方库时,一定要牢记同时安装这个库的类型声明文件。你可以从 TypeSearch 中找到并安装这些第三方库的类型声明文件。举个例子,如果想安装 lodash 这个库的类型声明文件,我们可以运行下面的命令:npm install --save-dev @types/lodash
  4. TypeScript学习
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • 写在开头 先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘...
    Lefter阅读 5,273评论 4 31
  • 作者:小 boy (沪江前端开发工程师)本文原创,转载请注明作者及出处。原文地址:https://www.smas...
    iKcamp阅读 2,742评论 0 18
  • 目录第1章 webpack简介 11.1 webpack是什么? 11.2 官网地址 21.3 为什么使用 web...
    lemonzoey阅读 1,731评论 0 1
  • 全局安装webpack 全局安装webpack会有个问题,就是当你有两个项目依赖于不同版本的webpack,就会有...
    説好的妹紙呢阅读 1,803评论 0 11
  • 今天上午去参加模拟面试,两个半小时,提了很多刁钻的问题,见识了很多人不同的答案。总结就一个宗旨,再有思想有调理的情...
    忽尔今至阅读 142评论 2 0