webpack

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

从webpack v4.0.0开始,可以不用引入一个配置文件。

四个核心概念:

  • 入口(entry)

    入口起点(entry point)指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的。

    每个依赖项随即被处理,最后输出到称之为bundles的文件中。

    可以在webpack配置中配置entry属性来指定一个或多个入口起点。默认值为./src:

      // webpack.config.js
      module.exports = {
          entry: './path/to/my/entry/file.js'
      };
    
  • 输出(output)

    output属性告诉webpack在哪里输出它所创建的bundles,以及如何命名这些文件,默认值为./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。可以在配置中指定output字段:

      // webpack.config.js
      const path = require('path');
      // path模块是一个Node.js核心模块,用于操作文件路径。
      module.exports = {
          entry: './path/to/my/entry/file.js',
          output: {
              path: path.resolve(__dirname, 'dist'),
              filename: 'my-first-webpack.bundle.js'
              // 通过output.filename和output.path属性告诉webpack bundle的名称,
              // 以及我们想要bundle生成(emit)到哪里。
          }
      };
    

    生成(emitted 或 emit),它是“生产(produced)”或“释放(discharged)”的特殊术语。

  • loader

    loader让webpack能够去处理那些非JavaScript文件(webpack自身只理解JavaScript)。loader可以将所有类型的文件转换为webpack能够处理的有效模块,然后就可以利用webpack的打包能力,对它们进行处理。

    本质上,webpack loader将所有类型的文件,转换为应用程序的依赖图(和最终的bundle)可以直接引用的模块。

    webpack配置中loader有两个目标:

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

      // webpack.config.js
      const path = require('path');
      
      const config = {
       output: {
           filename: 'my-first-webpack.bundle.js'
       },
       module: {
           rules: [
               { test: /\.txt$/, use: 'raw-loader' }
               // “嘿,webpack 编译器,
               // 当你碰到「在require()/import语句中被解析为'.txt'的路径」时,
               // 在你对它打包之前,先使用raw-loader转换一下。”
           ]
       }
      };
      
      module.exports = config;
      

      在webpack配置中定义loader时,要定义在module.rules中,而不是rules。

  • 插件(plugins)

    loader被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。

    想要使用一个插件,只需要require()它,然后把它添加到plugins数组中。多数插件可以通过选项(option)自定义。可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用new操作符来创建它的一个实例。

      // webpack.config.js
      const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
      const webpack = require('webpack'); // 用于访问内置插件
    
      const config = {
          module: {
              rules: [
                  { test: /\.txt$/, use: 'raw-loader' }
              ]
          },
          plugins: [
              new webpack.optimize.UglifyJsPlugin(),
              new HtmlWebpackPlugin({template: './src/index.html'})
          ]
      };
    
      module.exports = config;
    

    插件列表

  • 模式

    通过选择development或production来设置mode参数,可以启用相应模式下的webpack内置的优化。

      // webpack.config.js
      module.exports = {
          mode: 'production'
      };
    

入口起点(entry points)

back to top ▲

单个入口(简写)语法

entry: string|Array<string>

// webpack.config.js
const config = {
    entry: './path/to/my/entry/file.js'
};

module.exports = config;

// entry属性的单个入口语法是下面的简写:

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

使用此语法在扩展配置时有失灵活性。

对象语法

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

// webpack.config.js

const config = {
    entry: {
        app: './src/app.js',
        vendors: './src/vendors.js'
    }
};

这是应用程序中定义入口的最可扩展的方式。

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

常见场景

分离 应用程序(app) 和 第三方库(vendor) 入口

// webpack.config.js
const config = {
    entry: {
        app: './src/app.js',
        vendors: './src/vendors.js'
    }
};

webpack从app.js和vendors.js开始创建依赖图(dependency graph)。

这些依赖图是彼此完全分离、互相独立的。

这种方式比较常见于只有一个入口起点(不包括vendor)的单页应用程序(single page application)中。

多页面应用程序

// webpack.config.js
const config = {
    entry: {
        pageOne: './src/pageOne/index.js',
        pageTwo: './src/pageTwo/index.js',
        pageThree: './src/pageThree/index.js'
    }
};

在多页应用中,每当页面跳转时服务器将为你获取一个新的HTML文档。页面重新加载新文档,并且资源被重新下载。

输出(output)

back to top ▲

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

用法(Usage)

webpack.config.js

const config = {
    // output属性,将它的值设置为一个对象。
    output: {
        filename: 'bundle.js',
        // filename:用于输出文件的文件名。
        path: '/home/proj/public/assets'
        // path:目标输出目录的绝对路径。
    }
};

module.exports = config;

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

多个入口起点

如果配置创建了多个单独的"chunk"(例如,使用多个入口起点或使用像CommonsChunkPlugin这样的插件),则应该使用占位符(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的复杂示例
// webpack.config.js
output: {
  path: "/home/proj/cdn/assets/[hash]",
  publicPath: "http://cdn.example.com/assets/[hash]/"
}

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

__webpack_public_path__ = myRuntimePublicPath

// 剩余的应用程序入口

模式(mode)

back to top ▲

mode配置选项告诉webpack使用相应模式的内置优化。

用法

在配置中提供mode选项:

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

或者从CLI参数中传递:

webpack --mode=production
选项 描述
development 会将process.env.NODE_ENV的值设为development。启用NamedChunksPlugin和NamedModulesPlugin。
production 会将process.env.NODE_ENV的值设为production。启用FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin和UglifyJsPlugin。

只设置NODE_ENV,则不会自动设置mode。

mode: development

// webpack.development.config.js
module.exports = {
+ mode: 'development'
- plugins: [
-   new webpack.NamedModulesPlugin(),
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}

mode: production

// webpack.production.config.js
module.exports = {
+  mode: 'production',
-  plugins: [
-    new UglifyJsPlugin(/* ... */),
-    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
-    new webpack.optimize.ModuleConcatenationPlugin(),
-    new webpack.NoEmitOnErrorsPlugin()
-  ]
}

loader

back to top ▲

loader用于对模块的源代码进行转换。loader可以使你在import或“加载”模块时预处理文件。loader类似于其他构建工具中“任务(task)”。loader可以将文件从不同的语言(如TypeScript)转换为JavaScript,或将内联图像转换为data URL。loader甚至允许你直接在JavaScript模块中import CSS文件!

示例

你可以使用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

有三种使用loader的方式:

  • 配置(推荐):在webpack.config.js文件中指定loader;

    这是展示loader的一种简明方式,并且有助于使代码变得简洁。同时让你对各个loader有个全局概览:

      module: {
          rules: [
              {
                  test: /\.css$/,
                  use: [
                      { loader: 'style-loader' },
                      {
                          loader: 'css-loader',
                          options: {
                              modules: true
                          }
                      }
                  ]
              }
          ]
      }
    
  • 内联:在每个import语句中显式指定loader;

      // 使用 ! 将资源中的loader分开
      import Styles from 'style-loader!css-loader?modules=true!./styles.css';
    

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

  • CLI:在shell命令中指定它们。

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

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

my webpack.config.js

const webpack = require('webpack');
const path = require('path');

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                // babel-loader 慢怎么办:尽可能转译少的文件,排除 node_modules 文件夹下的文件
                exclude: /(node_modules|bower_components)/,
                use: {
                    // babel-loader 慢怎么办:配置 cacheDirectory,缓存 loader 的执行结果

                    // cacheIdentifier:默认是由 babel-core 版本号,
                    // babel-loader 版本号,.babelrc 文件内容(存在的情况下),
                    // 环境变量 BABEL_ENV 的值(没有时降级到 NODE_ENV)组成的字符串,
                    // 可以设置为一个自定义的值,在 identifier 改变后,强制缓存失效。
                    loader: 'babel-loader?cacheDirectory=babel_loader_cache&cacheIdentifier',
                    options: {
                        presets: ['@babel/preset-env'],

                        // babel 默认情况下会将一些辅助代码添加到每一个需要它的文件中,
                        // 引入 babel-plugin-transform-runtime 使所有辅助代码从这里引用,从而避免重复引入。
                        // npm install babel-plugin-transform-runtime --save-dev
                        // npm install babel-runtime --save
                        plugins: ['@babel/transform-runtime']
                    }
                }
            }
        ]
    }
}

results matching ""

    No results matching ""