# Webpack

# 中文文档 (opens new window)

# 为什么需要构建

  • 前端工程复杂化,文件越来越多

  • 框架演变:

js库——>MVC——>MV*(模块化)

  • HTML发展历史
  • CSS发展历史
  • 前端脚本语言

JS->CoffeeScript->ES2018->TS

  • 环境的变化

现在的前端代码,一份代码可能要跑在浏览器端、服务器端、移动端

  • 社区的变化

现在搜索资源可以在github/npm进行搜索

  • 构建工具的变化

GRUNT->GULP->WEBPACK

总结

  • 开发复杂化

  • 框架去中心化(非大而全)

  • 语言编译化(写的代码浏览器都不认识)

  • 开发模块化(模块化浏览器也不认识)

为什么Webpack?

  • Vue-cli/React-starter/Angular-cli都是基于webpack打包

  • 代码分割Code-splitting

  • 天生的模块化

# 模块化开发

babel编译ES6语法,模块化可用webpackrollup

# 1. JS模块化

  • 命名空间(上古年代)

库名.类别名.方法名 (现在看来就非常不方便了)

var NameSpace = {}

NameSpace.type = NameSpace.type || {}

NameSpace.type.method = function () {

}
  • CommonJS(node服务端使用)
 Modules/1.1.1

 一个文件为一个模块

 通过`module.exports`暴露模块接口

 通过`require`引入模块

 同步执行
  • AMD/CMD (浏览器中使用)
AMD:依赖前置,提前执行(Async Module Definition)

使用define定义模块

使用require加载模块

代表作:RequireJS # 这个库采用AMD规范

define(
  // 模块名
  "alpha",
  // 依赖
  ["require", "exports", "beta"],
  // 模块输出
  function (require, exports, beta) {
    exports.verb = function() {
      return beta.verb()
    }
  }
)

CMD:尽可能懒加载(Common Module Definition)

一个文件为一个模块

使用define来定义一个模块

使用require来加载一个模块

代表作:SeaJS

// 所有模块都通过define来定义
define(function(require, exports, module) {
  // 通过require引入依赖
  var $ = require('jquery')
  var Spinning = require('./spinning')

  // 通过module.exports提供整个接口
  module.exports = ...
})

UMD:通用解决防范

三个步骤:
  判断是否支持AMD
  判断是否支持CommonJS
  如果都没有,使用全局变量
  • ESM(现在用的,webpack原生支持)
EcmaScript  Module

一个文件一个模块

import:

import theDefault, {named1, named2} from 'src/mylib'
import {named1 as myNamed1, named2} from 'src/mylib'
import * as mylib form 'src/mylib'
import 'src/mylib' // 只加载

export:

export const myVar1 = ''
export function myFunc() { }
export class MyClass { }

export default 123
export default function (x) {
  return x
}
export default x => x
export default class {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
}

# CSS模块化

css设计模式 :OOCSS(面向对象)、原子css、属性css、BEM(Block Element Modifier)

# 安装webpack

  1. 全局安装
 npm install webpack -g

然后,会报错,显示没有权限

sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}

更改权限后,再次安装

npm install webpack-cli -g
  1. 项目安装
npm init

npm i webpack -D

# 版本更迭

1. Webpack V1(2014.2)

  • 编译、打包

  • HMR(模块热更新)

  • 代码分割

  • 文件处理(loader、plugin)

2. Webpack V2(2017.1)

  • Tree Shaking(会删除引入的,但并没有使用的代码,打包的代码体积更好)

  • ES module

  • 动态Import(import函数)

  • 新的文档

3. Webpack V3(2017.6)

  • Scope Hoisting(作用域提升)

作用:打包以后代码性能的提升

原理:以前的版本,是把每个模块单独包裹在一个函数的闭包中,实现模块系统,闭包越多对浏览器性能影响越大;在v3版本总,将代码的模块的作用域提到单一的作用域中,保证浏览器运行速度

  • Magic Comments(配合动态import使用,指定webpack可以懒加载这段代码,打包后不知道叫什么名字)

可以指定打包后的文件名叫什么/chunk名叫什么

4. Webpack V4(2018.2)

Webpack 4.0 发布:有哪些新特性?(译) (opens new window)

Webpack 4 不完全迁移指北 (opens new window)

# 核心概念

# Entry

代码的入口

打包的入口

可以是单个或者多个

module.exports = {
  entry: {
    index: 'index.js', // 这个key可以表示为一个独特的chunk
    vendor: 'vendor.js'
  }
} 

# Output

打包成的文件(bundle)

一个或者多个

自定义规则(hash名字)

配合cdn

module.exports = {
  output: {
    filename: '[name].[hash:5].js'
  }
}

# Loaders

处理文件(除js文件外,v4好像默认的文件更多了)

转化为js的一个模块,引入进来

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: 'css-loader'
      }
    ]
  }
}

常用Loader

编译相关:

babel-loader ts-loader

样式相关:

style-loader css-loader stylus-loader postcss-loader

文件相关:

file-loader url-loader

# Plugins

参与打包整个过程

打包优化和压缩

配置编译时的变量

极其灵活

const webpack = require('webpack')

module.exports = {
  plugins: [
    new webpack.optimize.UglifyJsPlugin(), // 混淆和压缩代码
    new HtmlWebpackPlugin({
      chunks: ['vant-docs'],
      template: 'docs/src/index.tpl',
      filename: 'index.html',
      inject: true
    })
  ]
}

常用Plugins

优化相关:

CommonsChunkPlugin(4中已被移除):用于建立一个独立文件(又称作 chunk)的功能

new webpack.optimize.CommonsChunkPlugin(options)

UglifyjsWebpackPlugin:能够删除未引用代码(dead code)的压缩工具(minifier),还可以生成source-map

功能相关:

ExtractTextWebpackPlugin:将所有的入口 chunk(entry chunks)中引用的 *.css,移动到独立分离的 CSS 文件。因此,你的样式将不再内嵌到 JS bundle 中,而是会放到一个单独的 CSS 文件(即 styles.css)当中。

HtmlWebpackPlugin:让插件为你生成一个HTML文件

模块热替换插件(HotModuleReplacementPlugin)

CopyWebpackPlugin:拷贝文件

# 几个名词

Chunk: 代码块,比如说懒加载的某个代码块、公共的代码块

Bundle: 打包过的

Module: 模块,比如图片处理完可以叫一个模块,css处理后可以叫一个模块

# 使用webpack

  1. webpack命令
webpack -h

webpack -v

webpack <entry> <output>
  1. webpack配置
webpack --config webpack.conf.dev.js(自定义的配置文件名)
  1. 第三方脚手架
vue-cli

angular-cli

react-starter

例子: 编译ES6/7

1. npm init

2. npm i webpack babel-preset-env babel-loader babel-core

3. "build": "webpack --config webpack.config.js"  

4. webpack.config.js

module.exports = {
  mode: 'development',
  entry: {
    app: './app.js'
  },
  output: {
    filename: '[name].[hash:8].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['babel-preset-env', {
                targets: {
                  browsers:['> 1%', 'last 2 versions']
                }
              }]
            ]
          }
        },
        exclude: '/node_modules/'
      }
    ]
  }
}

5. app.js

let func = () => {}
const NUM = 34
let arr = [1,2,3]
let arr2 = arr.map(item => item * 2)

console.log('new Set(arr2)', new Set(arr2))

6. npm run build

# 打包速度优化

# 分开vendor和app

减少第三方包的打包

# 压缩和混淆

UglifyJsPlugin传入parallel: true采用并行方式打包压缩

  new UglifyJsPlugin({
    sourceMap: true
  })

# HappyPack

# 减少babel的编译时间

减少resolve

Deltool:去除sourceMap

cache-loader

升级node

升级webpack

# Tree-Shaking

Tree-Shaking 关注于消除没有用到的代码。

Webpack 是基于 ES6 Modules 静态语法解析的构建工具,Tree-Shaking 之所以能够在 Webpack 实现,也是得益于 ES6 Modules 静态解析的特性。ES6 的模块声明保证了依赖关系是提前确定的,使得静态分析成为可能,这样在 Webpack 中代码不需要执行就可以知道是否被使用,自然就知道哪些是无用的代码了。

Webpack 中 Tree-Shaking 的实现步骤:

  1. Webpack 自己来分析 ES6 Modules 的引入和使用情况,去除不使用的import引入;
  2. 借助工具(如 uglifyjs-webpack-plugin和terser-webpack-plugin)进行删除,这些工具只在mode=production中会被使用。