【webpack】指南

官网指南地址javascript

管理资源&管理输出

管理资源

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

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配置以下:html

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
复制代码

开发模式&模块热替换

生产环境

  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 模块)",由此能够安全地删除文件中未使用的部分。vue

  1. sideEffects:将文件标记为无反作用 如同上面提到的,若是全部代码都不包含反作用,咱们就能够简单地将该属性标记为 false,来告知 webpack,它能够安全地删除未用到的 export 导出。
{
  "name": "your-project",
  "sideEffects": false
}
复制代码

若是你的代码确实有一些反作用,那么能够改成提供一个数组:java

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

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

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

非动态加载时的打包状况以下图: jquery

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

// import Hello from '@/components/Hello'
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      // component: Hello,
      component: () => import('@/components/Hello')
    }
  ]
})
复制代码

  • 很明显的看到,打包后有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 编译生成的文件可以被客户端缓存,而在文件内容变化后,可以请求到新的文件webpack

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

建立library

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

个人demo:element-ui表单的二次封装github

最后,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做为全局变量,能够被别的组件引用

2. 将模块中的this置为window

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

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

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

4. babel-polyfill 按需加载

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

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

  • babel-preset-env 是一个新的preset,能够根据配置的目标运行环境(environment)自动启用须要的 babel 插件
  • 以前咱们写 javascript 代码时,须要使用 Npreset,好比:babel-preset-es201五、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配置
  2. tsconfig.json案例:
  3. 使用第三方库 当从 npm 安装第三方库时,必定要牢记同时安装这个库的类型声明文件。你能够从 TypeSearch 中找到并安装这些第三方库的类型声明文件。举个例子,若是想安装 lodash 这个库的类型声明文件,咱们能够运行下面的命令:npm install --save-dev @types/lodash
  4. TypeScript学习
相关文章
相关标签/搜索