在线试读

get_product_contenthtml
1.1.4  个Webpack构建实战
本节将构建一个很简单的项目,重点是让读者先了解Webpack。新建项目app(一个空目录),并以此为根目录。
使用Webpack构建项目示例:
(1)在根目录下新建文件index.html,内容如下:
 
<!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>
</head>
<body>
    <script type="text/javascript" src="bundle.js" charset="utf-8"></script>
</body>
</html>
 
(2)新建文件a.js,内容如下:
document.write("It works from a.js");
 
此时目录结构为:
 
.
├── a.js
└── index.html
 
(3)在根目录下执行如下命令(Webpack 4以上版本):
 
webpack a.js -o bundle.js
?提示:如果是Webpack 4以下版本,则不需要-o参数,但需要webpack.config.js配置文件。
Webpack会根据模块的依赖关系进行静态分析,模块a.js被打包为bundle.js。终端效果如图1.3所示,同时根目录下会生成一个新的文件./bundle.js。
 
图1.3  Webpack编译文件
?提示:如果Webpack构建不成功,,查看源码中是否有特殊字符;第二,查看Webpack版本号。Webpack4版本开始的构建命令略有不同。
1.1.5  Webpack loader实战
使用Webpack loader构建项目示例:
在前面示例基础上对文本样式进行修改,在根目录下安装loader,命令如下:
 
npm install css-loader style-loader –D
 
(1)新建文件./webpack.config.js,代码如下,其中的rules用来配置loader。
 
module.exports = {
    entry: "./a.js",
    output: {
        path: __dirname,
        filename: "bundle.js"
    },
    mode: "production",
    module: {
        rules: [
            { test: /\.css$/, loader: "style-loader!css-loader" }
        ]
    }
};
 
(2)新建./style.css样式文件,代码如下:
 
body {
  color: red;
}
 
(3)在a.js中引入样式文件:
 
import "./style.css";
 
此时项目结构如下:
 
.
├── a.js
├── index.html
├── package.json
├── style.css
└── webpack.config.js
 
(4)在终端执行Webpack命令:
 
webpack
 
Webpack会默认找到webpack.config.js配置文件,直接执行里面的命令,显示结果如图1.4所示。
 
图1.4  Webpack编译之loader
(5)根目录下会再次生成bundle.js文件。打开浏览器后,黑色英文Hello World!的颜色将变为红色。
实际项目中,一般不会直接执行Webpack命令,而是在package.json中的scripts内配置:
 
"scripts": {
    "a" :"webpack --config ./webpack.config.js"
  },
 
其中,参数--config用于指定读取哪个配置文件。如果没有指定--config,Webpack会默认读取webpack.config.js或webpackfile.js文件。项目中通常会配置两三个Webpack配置文件,命名时一般会带上环境,如webpack.config.base.js,webpack.config.dev.js和webpack.config.prod.js。
然后在终端执行:
 
npm run a
?提示:如果默认安装的package.json文件名为package-lock.json,别忘记修改过来。
1.1.6  Webpack配置详解
前面已经使用过webpack.config.js中的一些配置,本节将详细介绍Webpack的主要配置项。
(1)模式mode:
 
mode: "production",   // 生产模式
mode: "development",   // 开发模式
 
(2)入口entry:
 
entry: "./app/entry", // 入口可以是字符串、对象或数组 
entry: ["./app/entry1", "./app/entry2"],
entry: {
    a: "./app/entry-a",
    b: ["./app/entry-b1", "./app/entry-b2"]
},
 
(3)出口output:
 
output: {   // Webpack如何输出结果的相关选项
    path: path.resolve(__dirname, "dist"), // 字符串
    // 所有输出文件的目标路径
    // 必须是路径(使用Node.js的path模块)
    filename: "bundle.js", // 字符串
    filename: "[name].js", // 用于多个入口点(entry point) 
    filename: "[chunkhash].js", // 用于长效缓存
    // 「入口分块(entry chunk)」的文件名模板
    publicPath: "/assets/", // 字符串    
    publicPath: "",
    publicPath: "https://cdn.example.com/",
    // 输出解析文件的目录,URL相当于HTML页面
    library: "MyLibrary", // string,
    // 导出库(exported library)的名称
    libraryTarget: "umd", // 通用模块定义
    libraryTarget: "commonjs2", // 作为exports的属性导出
    libraryTarget: "commonjs", // 作为exports的属性导出
    libraryTarget: "amd", // 使用amd定义方法来定义
    libraryTarget: "this", // 在this上设置属性
    libraryTarget: "var", // 变量定义于根作用域下
    libraryTarget: "assign", // 盲分配(blind assignment)
    libraryTarget: "window", // 在window对象上设置属性
    libraryTarget: "global", // 设置global 对象
    libraryTarget: "jsonp", // jsonp wrapper
    // 导出库(exported library)的类型
    /* 高级输出配置(点击显示) */    pathinfo: true, // boolean
    // 以文件形式异步加载模块配置项
    chunkFilename: "[id].js",
    chunkFilename: "[chunkhash].js", // 长效缓存(/guides/caching)
    // 「附加分块(additional chunk)」的文件名模板
    jsonpFunction: "myWebpackJsonp", // string
    // 用于加载分块的 JSONP 函数名
    sourceMapFilename: "[file].map", // string
    sourceMapFilename: "sourcemaps/[file].map", // string
    // 「source map 位置」的文件名模板
    devtoolModuleFilenameTemplate: "webpack:///[resource-path]", // string
    // 「devtool 中模块」的文件名模板
    devtoolFallbackModuleFilenameTemplate: "webpack:///[resource-path]?
        [hash]", // string
    // 「devtool 中模块」的文件名模板(用于冲突)
    umdNamedDefine: true, // boolean
    // 在 UMD 库中使用命名的 AMD 模块
    crossOriginLoading: "use-credentials", // 枚举
    crossOriginLoading: "anonymous",
    crossOriginLoading: false,
    // 指定运行时如何发出跨域请求问题
    /* 专家级输出配置(自行承担风险) */  
}
 
(4)module模块处理:
 
 module: {
    // 关于模块配置
    rules: [
      // 模块规则(配置 loader、解析器等选项)
      {
        test: /\.jsx?$/,
        include: [
          path.resolve(__dirname, "app")
        ],
        exclude: [
          path.resolve(__dirname, "app/demo-files")
        ],
        // 这里是匹配条件,每个选项都接收一个正则表达式或字符串
        // test和include具有相同的作用,都是必须匹配选项
        // exclude 是必不匹配选项(优先于test和include)
        // 实践:
        // - 只在test和文件名匹配 中使用正则表达式
        // - 在include和exclude中使用路径数组
        // - 尽量避免使用exclude,更倾向于使用include
        issuer: { test, include, exclude },
        // issuer 条件(导入源)
        enforce: "pre",
        enforce: "post",
        // 标识应用这些规则,即使规则覆盖(高级选项)
        loader: "babel-loader",
        // 应该应用的 loader,它相对上下文解析
        // 为了更清晰,-loader 后缀在Webpack 2中不再是可选的
        options: {
          presets: ["es2015"]
        },
        // loader的可选项
      },
      {
        test: /\.html$/,
        use: [
          // 应用多个loader和选项
          "htmllint-loader",
          {
            loader: "html-loader",
            options: {
              /* ... */
            }
          }
        ]
      },
      { oneOf: [ /* rules */ ] },
      // 只使用这些嵌套规则之一
      { rules: [ /* rules */ ] },
      // 使用所有嵌套规则(合并可用条件)
      { resource: { and: [ /* 条件 */ ] } },
      // 仅当所有条件都匹配时才匹配
      { resource: { or: [ /* 条件 */ ] } },
      { resource: [ /* 条件 */ ] },
      // 任意条件匹配时匹配(默认为数组)
      { resource: { not: /* 条件 */ } }
      // 条件不匹配时匹配
    ],
/* 高级模块配置(点击展示) */    
noParse: [
      /special-library\.js$/
    ],
    // 不解析这里的模块
    unknownContextRequest: ".",
    unknownContextRecursive: true,
    unknownContextRegExp: /^\.\/.*$/,
    unknownContextCritical: true,
    exprContextRequest: ".",
    exprContextRegExp: /^\.\/.*$/,
    exprContextRecursive: true,
    exprContextCritical: true,
    wrappedContextRegExp: /.*/,
    wrappedContextRecursive: true,
    wrappedContextCritical: false,
    // specifies default behavior for dynamic requests
  },
 
(5)resolve解析:
 
  resolve: {
    // 解析模块请求的选项(不适用于对 loader 解析)
    modules: [
      "node_modules",
      path.resolve(__dirname, "app")
    ],
    // 用于查找模块的目录
    extensions: [".js", ".json", ".jsx", ".css"],
    // 使用的扩展名
    alias: {
      // 模块别名列表
      "module": "new-module",
      // 起别名:"module" -> "new-module" 和 "module/path/file" -> "new-
          module/path/file"
      "only-module$": "new-module"
      // 起别名 "only-module" -> "new-module",但不匹配 "only-module/path/
          file" -> "new-module/path/file"
      "module": path.resolve(__dirname, "app/third/module.js"),
      // 起别名 "module" -> "./app/third/module.js" 和 "module/file" 会导致错误
      // 模块别名相对于当前上下文导入
    },
    /* 可供选择的别名语法(点击展示) */    
 alias: [
      {
        name: "module",
        // 旧的请求
        alias: "new-module",
        // 新的请求
        onlyModule: true
        // 如果为true,那么只有module是别名
        // 如果为false,则"module/inner/path" 也是别名
      }
    ],
    /* 高级解析选项(点击展示) */    symlinks: true,
    // 遵循符号链接(symlinks)到新位置
    descriptionFiles: ["package.json"],
    // 从 package 描述中读取的文件
    mainFields: ["main"],
    // 从描述文件中读取的属性
    // 当请求文件夹时
    aliasFields: ["browser"],
    // 从描述文件中读取的属性
    // 以对此 package 的请求起别名
    enforceExtension: false,
    // 如果为 true,请求必不包括扩展名
    // 如果为 false,请求可以包括扩展名
    moduleExtensions: ["-module"],
    enforceModuleExtension: false,
    // 类似 extensions/enforceExtension,但是用模块名替换文件
    unsafeCache: true,
    unsafeCache: {},
    // 为解析的请求启用缓存
    // 这是不安全的,因为文件夹结构可能会改动
    // 但是性能改善是很大的
    cachePredicate: (path, request) => true,
    // predicate function which selects requests for caching
    plugins: [
      ...
    ]
    // 应用于解析器的附加插件
  },
 
(6)performance打包后命令行如何展示性能提示,如果超过某个大小时是警告还是报错:
 
  performance: {
    hints: "warning", // 枚举 hints: "error", // 性能提示中抛出错误
    hints: false, // 关闭性能提示
    maxAssetSize: 200000, // 整数类型(以字节为单位)
    maxEntrypointSize: 400000, // 整数类型(以字节为单位)
    assetFilter: function(assetFilename) {
      // 提供资源文件名的断言函数
      return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
    }
  },
 
(7)devtool用于配置调试代码的方式,打包后的代码和原始代码存在较大的差异,此选项控制是否生成,以及如何生成sourcemap:
 
  devtool: "source-map", // enum  devtool: "inline-source-map",
  // 嵌入到源文件中
  devtool: "eval-source-map", // 将sourcemap嵌入到每个模块中
  devtool: "hidden-source-map", // sourcemap 不在源文件中引用
  devtool: "cheap-source-map", // 没有模块映射(module mappings)的
    sourcemap低级变体(cheap-variant)
  devtool: "cheap-module-source-map", // 有模块映射(module mappings)的
    sourcemap低级变体
  devtool: "eval", // 没有模块映射,而是命名模块。以牺牲细节达到快
  // 通过在浏览器调试工具(browser devtools)中添加元信息(meta info)增强调试
  // 牺牲了构建速度的sourcemap是详细的
 
(8)context基础目录,路径,用于从配置中解析入口起点(entry point)和loader:
 
context: __dirname, // string(路径!)
 
(9)target构建目标:
 
  target: "web", // 枚举  target: "webworker", // WebWorker
  target: "webworker" //webworker
  target: "node", // Node.js 通过require加载模块
  target: "async-node", // Node.js 通过fs和vm加载分块
  target: "node-webkit", // 编译为webkit可用,并且用jsonp去加载分块
  target: "electron-main", // electron,主进程(main process)
  target: "electron-renderer", // electron,渲染进程(renderer process)
  target: (compiler) => { /* ... */ }, // 自定义
 
(10)externals外部拓展:
 
  externals: ["react", /^@angular\//],  externals: "react",   // string(精
      确匹配)
  externals: /^[a-z\-] ($|\/)/, // 正则
  externals: { // 对象
    angular: "this angular", // this["angular"]
    react: { // 使用UMD规范
      commonjs: "react",
      commonjs2: "react",
      amd: "react",
      root: "React"
    }
  },
  externals: (request) => { /* ... */ return "commonjs " request }
 
(11)stats统计信息:
 
  stats: "errors-only",  
  stats: { //object
    assets: true,
    colors: true,
    errors: true,
    errorDetails: true,
    hash: true,
    ...
  },
 
(12)devServer配置本地运行环境:
 
  devServer: {
    proxy: { // 服务器代理
      '/api': 'http://localhost:3000'
    },
    contentBase: path.join(__dirname, 'public'), // boolean、string或array,
    服务器资源根目录
    compress: true, // 启用gzip压缩 
    historyApiFallback: true, // 返回404页面时定向到特定页面
    ...
  },
 
(13)plugins插件:
 
  plugins: [
    ...
  ],
 
(14)其他插件:
  // 附加插件列表
  /* 高级配置(点击展示) */  
  resolveLoader: { /* 等同于 resolve */ }
  // 独立解析选项的 loader
  parallelism: 1, // number
  // 限制并行处理模块的数量
  profile: true, // boolean数据类型
  // 捕获时机信息
  bail: true, //boolean
  // 在个错误出错时抛出,而不是无视错误
  cache: false, // boolean
  // 禁用/启用缓存
  watch: true, // boolean
  // 启用观察
  watchOptions: {
    aggregateTimeout: 1000, // in ms毫秒单位
    // 将多个更改聚合到单个重构建(rebuild)
    poll: true,
    poll: 500, // 间隔单位ms
    // 启用轮询观察模式
    // 必须用在不通知更改的文件系统中
    // 即 nfs shares(Network FileSystem)
  },
  node: {
    // Polyfills和mocks可以在非Node环境中运行Node.js环境代码
    // environment code in non-Node environments.
    console: false, // boolean | "mock"布尔值或"mock"
    global: true, // boolean | "mock"布尔值或"mock"
    process: true, // boolean布尔值
    __filename: "mock", // boolean | "mock"布尔值或"mock"
    __dirname: "mock", // boolean | "mock"布尔值或"mock"
    Buffer: true, // boolean | "mock"布尔值或"mock"
    setImmediate: true // boolean | "mock" | "empty"布尔值或"mock"或"empty"
  },
  recordsPath: path.resolve(__dirname, "build/records.json"),
  recordsInputPath: path.resolve(__dirname, "build/records.json"),
  recordsOutputPath: path.resolve(__dirname, "build/records.json"),
  // TODO
}
 
更多详情可请访问Webpack官方网站https://webpack.js.org/。当然读者也可以直接使用Facebook官方提供的create-react-app进行搭建(https://github.com/facebook/create- react-app)。后面章节会继续介绍Webpack真实项目的搭建与实践开发。