# WebPack

# WebPack 是什么?

从本质上来说,webpack 是一个静态模块打包工具。

要想让我们写好的模块化代码在各式各样的浏览器上能做到兼容,就必须借助于其他工具;

而 webpack 的其中一个核心就是让我们可以进行模块化开发,并帮我们处理模块间的依赖关系。

不仅仅是 Javascript 文件,我们的 css、图片、json 文件等在 webpack 中都可以当作模块来使用,这就是 webpack 的模块化概念。

当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

# 学习资料

Webpack 中文文档 (opens new window)

# 快速上手

# 简单的例子

假设我们有这样两个文件,一个 index.html 一个 src/index.js

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script src="./dist/main.js"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
document.getElementById("app").innerText = "Hello, Webpack";

# 安装依赖

# 本地安装
npm install webpack webpack-cli -D
// webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  entry: path.join(__dirname, "./src/index.js"), // dirname代表索引到文件所在目录
  output: {
    path: path.join(__dirname, "./dist"),
    filename: "main.js"
  }
};

在上面的示例中,我们通过 output.filenameoutput.path 属性,来告诉 webpack 打包出 bundle 的名称,以及我们想要把 bundle 生成到哪里

{
  "scripts": {
    "build": "webpack --config webpack.config.js"
  }
}
npm run build

# 热更新

npm install webpack-dev-server -D
npm install html-webpack-plugin -D
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development", // development-开发环境。 production-生产环境
  entry: path.join(__dirname, "./index.js"),
  output: {
    path: path.join(__dirname, "./dist"),
    filename: "main.js"
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "./index.html")
    })
  ]
};
{
  "scripts": {
    "dev": "webpack-dev-server --open --port 3002 --hot",
    "build": "webpack --config webpack.config.js"
  }
}
npm run dev

# 处理 CSS

# 处理 ES6

# 处理图片资源

# HTML 文件的热更新

# WebPack 核心概念

# 什么 Webpack 模块 ?

  • 在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块。
  • 每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。 精心编写的模块提供了可靠的抽象和封装界限,使得应用程序中每个模块都具有条理清楚的设计和明确的目的。
  • 在 web 存在多种支持 JavaScript 模块化的工具,这些工具各有优势和限制。webpack 基于从这些系统获得的经验教训,并将模块的概念应用于项目中的任何文件。

# entry

entry 的基本配置是一个指向文件的地址字符串

const config = {
  entry: {
    main: "./path/to/my/entry/file.js"
  }
};
// 简写
const config = {
  entry: "./path/to/my/entry/file.js"
};
// 数组
const config = {
  entry: ["./path/to/my/entry/file1.js", "./path/to/my/entry/file2.js"]
};
// 对象
const config = {
  entry: {
    app: "./src/app.js",
    vendors: "./src/vendors.js"
  }
};
// 多页面应用程序
const config = {
  entry: {
    pageOne: "./src/pageOne/index.js",
    pageTwo: "./src/pageTwo/index.js",
    pageThree: "./src/pageThree/index.js"
  }
};

对象语法会比较繁琐。然而,这是应用程序中定义入口的最可扩展的方式。

# output

outout 的基本配置是一个对象,至少包含以下两点:

  • filename 输出的文件名
  • path 输出路径
// 基本配置
const config = {
  output: {
    filename: "bundle.js",
    path: "/home/proj/public/assets"
  }
};
// 使用占位符来对应多个入口
const config = {
  entry: {
    app: "./src/app.js",
    search: "./src/search.js"
  },
  output: {
    filename: "[name].js",
    path: __dirname + "/dist"
  }
};
// 上面将会输出 ./dist/app.js ./dist/search.js

// 在进一步,我们使用哈希值来命名打包后的文件
const config = {
  entry: {
    app: "./src/app.js"
  },
  output: {
    filename: "[name].[hash].js",
    path: __dirname + "/dist"
  }
};
// 上面将会输出 ./dist/app.sdf4whq.sj 这里的哈希值却决于真实的文件内容

# mode

提供 mode 配置选项,会告知 webpack 使用相应模式的内置优化,默认支持两种模式分别是developmentproduction

// config中指定
const config = {
  mode: "production"
};
# 也可以从CLI参数中传递
webpack --mode=production

当指定mode的时候会将process.evn.NODE_ENV设置为相同值,反之值设置NODE_ENV则不会自动设置mode

# loader

webpack 自身只能处理 JavaScript,loader 让 webpack 能够处理非 JavaScript 文件
本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

在 webpack 中配置 loader 有两个目标:

  • test,正则表达式,用于标识出对应的 loader 进行转换的某个或某些文件
  • use,字符串,表示进行转换时,应该使用那个 loader

    注意在 webpack 中定义 loader 时,要定义在 module.rules 中,而不是 rules

// webpack.config.js
const path = require("path");
const config = {
  ouput: {
    filename: "bundle.js"
  },
  module: {
    rules: [
      { text: "/.txt$/", use: "raw-loader" },
      { test: "/.css$/", use: ["css-loader"] },
      { test: "/.ts$/", use: ["ts-loader"] },
      { test: "/.less$/", use: ["less-loader"] },
      { test: "/.sass$/", use: ["sass-loader"] },
      { test: "/.(mov|mp4)$/", use: ["file-loader"] }
    ]
  }
};

常用 loader 如下:

  • raw-loader val-loader url-loader file-loader
  • json-loader json5-loader cson-loader
  • script-loader babe-loader ts-loader coffee-loader
  • html-loader markdown-loader react-markdown-loader
  • style-loader css-loader less-loader sass-loader postcss-loader
  • eslint-loader mocha-loader jslint-loader
  • vue-loader angular-template-loader

module.rules允许指定多个 loader,这是展示 loader 的一种简明方式,并且有助于使代码变得简洁。

const config = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: "style-loader" },
          {
            loader: "css-loader",
            options: {
              modules: true
            }
          }
        ]
      }
    ]
  }
};

# plugins

插件是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上! 插件目的在于解决 loader 无法实现的其他事情

webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。

由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入 new 实例。

下面是一个 html-webpack-plugin 的使用示例:

const HtmlWebpackPlugin = require("html-webpack-plugin"); //通过 npm 安装
const webpack = require("webpack"); //访问内置的插件
const path = require("path");

const config = {
  entry: "./path/to/my/entry/file.js",
  output: {
    filename: "my-first-webpack.bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: "babel-loader"
      }
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      scriptLoad: "blocking"
    })
  ]
};

module.exports = config;

# Webpack 基础配置

# 配置对象

webpack 需要传入一个配置对象,那么我们就可以朴素的传入一个对象

var path = require("path");

module.exports = {
  mode: "development",
  entry: "./foo.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "foo.bundle.js"
  }
};

除了导出单个配置对象,还有一些方式满足其他需求
比如导出为一个函数

module.exports = function (env, argv) {
  return {
    mode: env.production ? "production" : "development",
    devtool: env.production ? "source-maps" : "eval",
    plugins: [
      new webpack.optimize.UglifyJsPlugin({
        compress: argv["optimize-minimize"] // 只有传入 -p 或 --optimize-minimize
      })
    ]
  };
};

或者导出一个 Promise

module.exports = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        entry: "./app.js"
        /* ... */
      });
    }, 5000);
  });
};

# Webpack 常用的加载器

# file-loader

# url-loader

# css-loader

# sass-loader

# vue-loader

# Webpack 常用的插件

# JSON 合并

merge-jsons-webpack-plugin (opens new window)

# 全局样式

style-resources-loader (opens new window)

# 依赖分析

webpack-bundle-analyzer (opens new window)1

# 条件编译

安装相关依赖

npm i js-conditional-compile-loader -D
npm i cross-env -D

修改打包配置

// webpack.base.conf.js
const conditionalCompiler = {
  loader: "js-conditional-compile-loader",
  options: {
    primary: process.env.version == "primary",
    senior: process.env.version == "senior",
    normal: process.env.oem == "", // undefined
  }
};
// webpack.base.conf.js
rules: [
  {
    test: /\.vue$/,
    // loader: "vue-loader",
    // options: vueLoaderConfig
    use: [
      {
        loader: "vue-loader",
        options: vueLoaderConfig
      },
      conditionalCompiler
    ]
  },
  {
    test: /\.js$/,
    // loader: "babel-loader",
    use: ["babel-loader", conditionalCompiler],
    include: [resolve("src"), resolve("test"), resolve("node_modules/webpack-dev-server/client")]
  }
];

package.json

通过命令行传入参数

{
  "dev:primary": "cross-env version=primary npm run dev",
  "dev:senior": "cross-env version=senior npm run dev"
}

在 JS 和 Vue 文件中使用

<template>
  <div id="app">
    <!-- /* IFTRUE_normal */ -->
    <img src="./assets/logo.png" />
    <!-- /* FIDEBUG_normal */ -->
    <!-- /* IFTRUE_youku */ -->
    <img src="./assets/logo_senior.png" />
    <!-- /* FITRUE_youku */ -->
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App",
  created() {
    /* IFTRUE_primary */
    console.log("primary");
    /* FITRUE_primary */
  }
};
</script>