webpack 知识概述

参考网站

https://www.webpackjs.com/concepts/entry-points/

entry

单个入口(简写)语法

用法:entry: string|Array<string>

webpack.config.js

const config = {
  entry: {
    main: './path/to/my/entry/file.js'
    // main: ['./path/to/my/entry/file.js','./path/to/my/entry/file2.js']
  }
};

// 简写

const config = {
  // 单文件依赖
  entry: './path/to/my/entry/file.js'
  // 多文件依赖,打包进一个入口
  // entry: ['./path/to/my/entry/file.js','./path/to/my/entry/file2.js']
};

module.exports = config;

 

对象语法

用法:entry: {[entryChunkName: string]: string|Array<string>}

单入口

webpack.config.js

// 下例的app和vendors是分支名,分离 应用程序(app) 和 第三方库(vendor) 入口

const config = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
    // 数组形式
    // vendors: ['./src/vendors.js','./src/vendors2.js']
  }
};

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

“可扩展的 webpack 配置”是指,可重用并且可以与其他配置组合使用。这是一种流行的技术,用于将关注点(concern)从环境(environment)、构建目标(build target)、运行时(runtime)中分离。然后使用专门的工具(如 webpack-merge)将它们合并。

多入口

在多页应用中,(译注:每当页面跳转时)服务器将为你获取一个新的 HTML 文档。页面重新加载新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事:

export let multipleEntry= {

    pageOne: './src/pageOne/index.js',

    pageTwo: './src/pageTwo/index.js',

    pageThree: './src/pageThree/index.js'

}

注意:使用 CommonsChunkPlugin插件 为每个页面间的应用程序共享代码创建 bundle。

output

配置 output 选项可以控制 webpack 如何向硬盘写入编译文件。即使可以存在多个入口起点,但只指定一个输出配置

用法(Usage)

在 webpack 中配置 output 属性的最低要求是,将它的值设置为一个对象,包括以下两点:

  • filename :用于输出文件的文件名。
  • path:目标输出目录 path 的绝对路径。

单个入口

webpack.config.js

const config = {
  output: {
    filename: 'bundle.js',
    path: '/home/proj/public/assets'
  }
};

module.exports = config;

此配置将一个单独的 bundle.js 文件输出到 /home/proj/public/assets 目录中。

多个入口

如果配置创建了多个单独的 "chunk",使用占位符(substitutions)来确保每个文件具有唯一的名称。

{
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist'
  }
}

// 写入到硬盘:./dist/app.js, ./dist/search.js

高级进阶

以下是使用 CDN 和资源 hash 的复杂示例:

config.js

output: {
  path: "/home/proj/cdn/assets/[hash]",
  publicPath: "http://cdn.example.com/assets/[hash]/"
}

在编译时不知道最终输出文件的 publicPath 的情况下,publicPath 可以留空,并且在入口起点文件运行时动态设置。如果你在编译时不知道 publicPath,你可以先忽略它,并且在入口起点设置 __webpack_public_path__

__webpack_public_path__ = myRuntimePublicPath

// 剩余的应用程序入口

mode

提供 mode:string<string> 配置选项,告知 webpack 使用相应模式的内置优化。

用法

只在配置中提供 mode 选项:

module.exports = {
  mode: 'production'
};

或者从 CLI 参数中传递:

webpack --mode=production

loader

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。

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

  1. test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
  2. use 属性,表示进行转换时,应该使用哪个 loader。

示例

例如,你可以使用 loader 告诉 webpack 加载 CSS 文件,或者将 TypeScript 转为 JavaScript。为此,首先安装相对应的 loader:

npm install --save-dev css-loader
npm install --save-dev ts-loader

然后指示 webpack 对每个 .css 使用 css-loader,以及对所有 .ts 文件使用 ts-loader

webpack.config.js

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

使用loader

配置[Configuration]

module.rules 允许你在 webpack 配置中指定多个 loader。 这是展示 loader 的一种简明方式,并且有助于使代码变得简洁。同时让你对各个 loader 有个全局概览:

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          }
        ]
      }
    ]
  }

内联

可以在 import 语句或任何等效于 "import" 的方式中指定 loader。使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。

import Styles from 'style-loader!css-loader?modules!./styles.css';

通过前置所有规则及使用 !,可以对应覆盖到配置中的任意 loader。

选项可以传递查询参数,例如 ?key=value&foo=bar,或者一个 JSON 对象,例如 ?{"key":"value","foo":"bar"}

尽可能使用 module.rules,因为这样可以减少源码中的代码量,并且可以在出错时,更快地调试和定位 loader 中的问题。

CLI

你也可以通过 CLI 使用 loader:

webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'

这会对 .jade 文件使用 jade-loader,对 .css 文件使用 style-loader 和 css-loader

loader 特性

  • loader 支持链式传递。能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。
  • loader 可以是同步的,也可以是异步的。
  • loader 运行在 Node.js 中,并且能够执行任何可能的操作。
  • loader 接收查询参数。用于对 loader 传递配置。
  • loader 也能够使用 options 对象进行配置。
  • 除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
  • 插件(plugin)可以为 loader 带来更多特性。
  • loader 能够产生额外的任意文件。

loader 通过(loader)预处理函数,为 JavaScript 生态系统提供了更多能力。 用户现在可以更加灵活地引入细粒度逻辑,例如压缩、打包、语言翻译和其他更多

 

插件(plugins)

插件目的在于解决 loader 无法实现的其他事

原理

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

ConsoleLogOnBuildWebpackPlugin.js

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
    apply(compiler) {
        compiler.hooks.run.tap(pluginName, compilation => {
            console.log("webpack 构建过程开始!");
        });
    }
}

compiler hook 的 tap 方法的第一个参数,应该是驼峰式命名的插件名称。建议为此使用一个常量,以便它可以在所有 hook 中复用。

用法

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

配置

webpack.config.js

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'})
  ]
};

module.exports = config;

Node API的使用

即便使用 Node API,用户也应该在配置中传入 plugins 属性。compiler.apply 并不是推荐的使用方式。

some-node-script.js

  const webpack = require('webpack'); //访问 webpack 运行时(runtime)
  const configuration = require('./webpack.config.js');

  let compiler = webpack(configuration);
  compiler.apply(new webpack.ProgressPlugin());

  compiler.run(function(err, stats) {
    // ...
  });

你知道吗:以上看到的示例和 webpack 自身运行时(runtime) 极其类似。wepback 源码中隐藏有大量使用示例,你可以用在自己的配置和脚本中。

配置

 webpack 的配置文件,是导出一个对象的 JavaScript 文件。

 webpack 配置是标准的 Node.js CommonJS 模块,你可以做到以下事情

  • 通过 require(...) 导入其他文件
  • 通过 require(...) 使用 npm 的工具函数
  • 使用 JavaScript 控制流表达式,例如 ?: 操作符
  • 对常用值使用常量或变量
  • 编写并执行函数来生成部分配置

但应避免以下做法

  • 在使用 webpack 命令行接口(CLI)(应该编写自己的命令行接口(CLI),或使用 --env)时,访问命令行接口(CLI)参数
  • 导出不确定的值(调用 webpack 两次应该产生同样的输出文件)
  • 编写很长的配置(应该将配置拆分为多个文件)

多种配置类型(configuration types)

你会发现需要在开发生产构建之间,消除 webpack.config.js 的差异。

导出为一个函数

从 webpack 配置文件中导出一个函数。该函数在调用时,可传入两个参数:

-module.exports = {
+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
       })
     ]
+  };
}

导出多个配置对象

你可能需要导出多个配置对象(从 webpack 3.1.0 开始支持导出多个函数)。当运行 webpack 时,所有的配置对象都会构建。

例如,导出多个配置对象,对于针对多个构建目标(例如 AMD 和 CommonJS)打包一个 library 非常有用。

module.exports = [{
  output: {
    filename: './dist-amd.js',
    libraryTarget: 'amd'
  },
  entry: './app.js',
  mode: 'production',
}, {
  output: {
    filename: './dist-commonjs.js',
    libraryTarget: 'commonjs'
  },
  entry: './app.js',
  mode: 'production',
}]

异步地加载所需的配置变量

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

模块(modules)

什么是 webpack 模块

对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:

  • ES2015 import 语句
  • CommonJS require() 语句
  • AMD define 和 require 语句
  • css/sass/less 文件中的 @import 语句
  • 样式(url(...))或 HTML 文件(<img src=...>)中的图片链接(image url)

支持的模块类型

webpack 通过 loader 可以支持各种语言和预处理器编写模块。loader 描述了 webpack 如何处理 非 JavaScript(non-JavaScript) _模块_,并且在 bundle 中引入这些依赖。 包括:

模块解析(module resolution)

resolver 是一个库(library),用于帮助找到模块的绝对路径。一个模块可以作为另一个模块的依赖模块,然后被后者引用

resolver 帮助 webpack 找到 bundle 中需要引入的模块代码,这些代码在包含在每个 require/import 语句中。 

当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径

webpack 中的解析规则

使用 enhanced-resolve,webpack 能够解析三种文件路径:

绝对路径

import "/home/me/file";

import "C:\\Users\\me\\file";

由于我们已经取得文件的绝对路径,因此不需要进一步再做解析。

相对路径

import "../src/file1";
import "./file2";

在这种情况下,使用 import 或 require 的资源文件(resource file)所在的目录被认为是上下文目录(context directory)。在 import/require 中给定的相对路径,会添加此上下文路径(context path),以产生模块的绝对路径(absolute path)。

模块路径

import "module";
import "module/lib/file";

模块将在 resolve.modules 中指定的所有目录内搜索。 你可以替换初始模块路径,此替换路径通过使用 resolve.alias 配置选项来创建一个别名。

一旦根据上述规则解析路径后,解析器(resolver)将检查路径是否指向文件或目录。如果路径指向一个文件

  • 如果路径具有文件扩展名,则被直接将文件打包。
  • 否则,将使用 [resolve.extensions] 选项作为文件扩展名来解析,此选项告诉解析器在解析中能够接受哪些扩展名(例如 .js.jsx)。

如果路径指向一个文件夹,则采取以下步骤找到具有正确扩展名的正确文件

  • 如果文件夹中包含 package.json 文件,则按照顺序查找 resolve.mainFields 配置选项中指定的字段。并且 package.json 中的第一个这样的字段确定文件路径。
  • 如果 package.json 文件不存在或者 package.json 文件中的 main 字段没有返回一个有效路径,则按照顺序查找 resolve.mainFiles 配置选项中指定的文件名,看是否能在 import/require 目录下匹配到一个存在的文件名。
  • 文件扩展名通过 resolve.extensions 选项采用类似的方法进行解析。

webpack 根据构建目标(build target)为这些选项提供了合理的默认配置。

依赖图(dependency graph)

任何时候,一个文件依赖于另一个文件,webpack 就把此视为文件之间有 依赖关系 。这使得 webpack 可以接收非代码资源(non-code asset)(例如图像或 web 字体),并且可以把它们作为 _依赖_ 提供给你的应用程序。

 从配置文件的entry(入口起点) 开始,webpack 递归地构建一个 依赖图 ,这个依赖图包含着应用程序所需的每个模块,然后将所有这些模块打包为少量的 bundle - 通常只有一个 - 可由浏览器加载。

webpack 主要的代码类型

在使用 webpack 构建的典型应用程序或站点中,有三种主要的代码类型

  1. 你或你的团队编写的源码。
  2. 你的源码会依赖的任何第三方的 library 或 "vendor" 代码。
  3. webpack 的 runtime 和 manifest,管理所有模块的交互。

本文将重点介绍这三个部分中的最后部分,runtime 和 manifest。

Runtime

主要是指:在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。

runtime 包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。

Manifest

那么,一旦你的应用程序中,形如 index.html 文件、一些 bundle 和各种资源加载到浏览器中,会发生什么?你精心安排的 /src 目录的文件结构现在已经不存在,所以 webpack 如何管理所有模块之间的交互呢?这就是 manifest 数据用途的由来……

编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点这个数据集合称为 "Manifest",当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块。无论你选择哪种模块语法那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。

使用场景

如果你决定通过使用浏览器缓存来改善项目的性能,理解这一过程将突然变得尤为重要。

即使表面上某些内容没有修改,计算出的哈希还是会改变。这是因为,runtime 和 manifest 的注入在每次构建都会发生变化。

构建目标(targets)

target:string | function(compiler)

告知 webpack 为目标(target)指定一个环境。

string

通过 WebpackOptionsApply ,可以支持以下字符串值:

例如,当 target 设置为 "electron"webpack 引入多个 electron 特定的变量。有关使用哪些模板和 externals 的更多信息,你可以直接参考 webpack 源码

function

如果传入一个函数,此函数调用时会传入一个 compiler 作为参数。如果以上列表中没有一个预定义的目标(target)符合你的要求,请将其设置为一个函数。

例如,如果你不需要使用以上任何插件:

const options = {
  target: () => undefined
};

或者可以使用你想要指定的插件

const webpack = require("webpack");

const options = {
  target: (compiler) => {
    compiler.apply(
      new webpack.JsonpTemplatePlugin(options.output),
      new webpack.LoaderTargetPlugin("web")
    );
  }
};

webpack 的 target 属性不要和 output.libraryTarget 属性混淆。有关 output 属性的更多信息,请查看我们的指南

多个 Target

尽管 webpack 不支持向 target 传入多个字符串,你可以通过打包两份分离的配置来创建同构的库:

webpack.config.js

var path = require('path');
var serverConfig = {
  target: 'node',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.node.js'
  }
  //…
};

var clientConfig = {
  target: 'web', // <=== 默认是 'web',可省略
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.js'
  }
  //…
};

module.exports = [ serverConfig, clientConfig ];

上面的例子将在你的 dist 文件夹下创建 lib.js 和 lib.node.js 文件。

资源

下面是一个示例列表,以及你可以参考的资源。

模块热替换(hot module replacement)

会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。

流程

todo...

启用 HMR

启用此功能实际上相当简单。而我们要做的,就是更新 webpack-dev-server 的配置,和使用 webpack 内置的 HMR 插件。

如果你使用了 webpack-dev-middleware 而没有使用 webpack-dev-server,请使用 webpack-hot-middleware package 包,以在你的自定义服务或应用程序上启用 HMR。

webpack.config.js

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

  module.exports = {
    entry: {
       app: './src/index.js'
    },
    devtool: 'inline-source-map',
    devServer: {  // webpack-dev-server配置
      contentBase: './dist',
+     hot: true   // 配置启用HMR
    },
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Hot Module Replacement'
      }),
+     new webpack.NamedModulesPlugin(), // 以便更容易查看要修补(patch)的依赖。
+     new webpack.HotModuleReplacementPlugin() // 加载热代替模块
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

通过 Node.js API

当使用 webpack dev server 和 Node.js API 时,不要将 dev server 选项放在 webpack 配置对象(webpack config object)中。而是,在创建选项时,将其作为第二个参数传递。例如:

new WebpackDevServer(compiler, options)

想要启用 HMR,还需要修改 webpack 配置对象,使其包含 HMR 入口起点。webpack-dev-server package 中具有一个叫做 addDevServerEntrypoints 的方法,你可以通过使用这个方法来实现。这是关于如何使用的一个小例子:

dev-server.js

const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');

const config = require('./webpack.config.js');
const options = {
  contentBase: './dist', 
  hot: true,  // 启用hot
  host: 'localhost'
};

webpackDevServer.addDevServerEntrypoints(config, options); // HMR 入口起点
const compiler = webpack(config);
const server = new webpackDevServer(compiler, options);

server.listen(5000, 'localhost', () => {
  console.log('dev server listening on port 5000');
});

HMR 修改样式表

借助于 style-loader 的帮助,CSS 的模块热替换实际上是相当简单的。当更新 CSS 依赖模块时,此 loader 在后台使用 module.hot.accept 来修补(patch) <style> 标签。

npm install --save-dev style-loader css-loader
module: {
+     rules: [
+       {
+         test: /\.css$/,
+         use: ['style-loader', 'css-loader']
+       }
+     ]
+   },

其他代码和框架

社区还有许多其他 loader 和示例,可以使 HMR 与各种框架和库(library)平滑地进行交互……

  • React Hot Loader:实时调整 react 组件。
  • Vue Loader:此 loader 支持用于 vue 组件的 HMR,提供开箱即用体验。
  • Elm Hot Loader:支持用于 Elm 程序语言的 HMR。
  • Redux HMR:无需 loader 或插件!只需对 main store 文件进行简单的修改。
  • Angular HMR:No loader necessary! A simple change to your main NgModule file is all that's required to have full control over the HMR APIs.没有必要使用 loader!只需对主要的 NgModule 文件进行简单的修改,由 HMR API 完全控制。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值