• webpack:详解entry和output一些重要API的使用


    context

    说 entry 和 output 之前要先说一下 context,也就是上下文对象。

    Webpack 在寻找相对路径的文件时会以 context 为根目录,context 默认为执行启动 Webpack 时所在的当前工作目录。 如果想改变 context 的默认配置,则可以在配置文件里这样设置它:

    module.exports = {
      context: path.resolve(__dirname, 'app')
    }
    
    • 1
    • 2
    • 3

    之所以在这里先介绍 context,是因为 Entry 的路径和其依赖的模块的路径可能采用相对于 context 的路径来描述,context 会影响到这些相对路径所指向的真实文件。

    entry

    单个入口

    module.exports = {
      entry: {
        main: './path/to/my/entry/file.js',
      },
    };
    // 简写
    module.exports = {
      entry: './path/to/my/entry/file.js',
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    所以可以看到,我们使用默认配置的时候,也就是 output 不指定输出的名称,那么打包出的文件是 main.js,就是因为我们入口这里默认是 main。

    多个入口

    两种写法

    module.exports = {
      entry: ['./src/file_1.js', './src/file_2.js']
    };
    
    module.exports = {
      entry: {
        app: './src/app.js',
        adminApp: './src/adminApp.js',
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    对象写法的好处是可扩展性强,可以将其拆分然后用专门的工具(如 webpack-merge)将它们合并起来。

    entry相关API

    • dependOn: 当前入口所依赖的入口。它们必须在该入口被加载前被加载。
    • filename: 指定要输出的文件名称。
    • import: 启动时需加载的模块。
    • library: 指定 library 选项,为当前 entry 构建一个 library。
    • runtime: 运行时 chunk 的名字。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunk。
    • publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址。请查看 output.publicPath。

    这些 API 很少用到,而且这些主要是用来做代码分割的,而我们做代码分割一般用 optimization.splitChunks 来做,可以看我这篇文章

    例一

    module.exports = {
      //...
      entry: {
        home: './home.js',
        shared: ['react', 'react-dom', 'redux', 'react-redux'],
        catalog: {
          import: './catalog.js',
          filename: 'pages/catalog.js',
          dependOn: 'shared',
          chunkLoading: false, // Disable chunks that are loaded on demand and put everything in the main chunk.
        },
        personal: {
          import: './personal.js',
          filename: 'pages/personal.js',
          dependOn: 'shared',
          chunkLoading: 'jsonp',
          asyncChunks: true, // Create async chunks that are loaded on demand.
          layer: 'name of layer', // set the layer for an entry point
        },
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    例二

    使用dependOn,app 这个 chunk 就不会包含 react-vendors 拥有的模块了.

    module.exports = {
      //...
      entry: {
        app: { import: './app.js', dependOn: 'react-vendors' },
        'react-vendors': ['react', 'react-dom', 'prop-types'],
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    dependOn 选项的也可以为字符串数组:

    module.exports = {
      //...
      entry: {
        moment: { import: 'moment-mini', runtime: 'runtime' },
        reactvendors: { import: ['react', 'react-dom'], runtime: 'runtime' },
        testapp: {
          import: './wwwroot/component/TestApp.tsx',
          dependOn: ['reactvendors', 'moment'],
        },
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    例三

    加载动态入口

    // 同步
    module.exports = {
      //...
      entry: () => './demo',
    };
    
    // 异步
    module.exports = {
      //...
      entry: () => new Promise((resolve) => resolve(['./demo', './demo2'])),
    };
    
    // 异步接口加载
    module.exports = {
      entry() {
        return fetchPathsFromSomeExternalSource(); // 返回一个会被用像 ['src/main-layout.js', 'src/admin-layout.js'] 的东西 resolve 的 promise
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    output

    output.assetModuleFilename

    静态资源文件名称,如图片字体图标等
    默认: string = '[hash][ext][query]'
    可以使用 :[name], [file], [query], [fragment], [base] 与 [path]

    const path = require('path');
    
    module.exports = {
      entry: './src/index.js',
      output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
       assetModuleFilename: 'images/[hash][ext][query]'
      },
      module: {
        rules: [
          {
            test: /\.png/,
            type: 'asset/resource'
          }
        ]
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    说句题外话,一般不会在 output 中改变资源名称,而是在这里

    const path = require('path');
    
    module.exports = {
     entry: './src/index.js',
     output: {
       filename: 'main.js',
       path: path.resolve(__dirname, 'dist'),
       assetModuleFilename: 'images/[hash][ext][query]'
     },
     module: {
       rules: [
         {
           test: /\.png/,
           type: 'asset/resource'
        }
        },
        {
          test: /\.html/,
          type: 'asset/resource',
          // 这里
          generator: {
            filename: 'static/[hash][ext][query]'
          }
        }
       ]
     },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    output.chunkFilename

    输出的 chunk 文件名。

    module.exports = {
      //...
      output: {
        chunkFilename: (pathData) => {
          return pathData.chunk.name === 'main' ? '[name].js' : '[name]/[name].js';
        },
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    一般我们也不用这个设置,而是用 optimization.splitChunks 来做,可以看我这篇文章

    output.clean【5.20.0+版本支持】

    清除打包后的资源,和 CleanWebpackPlugin 插件作用差不多。

    module.exports = {
      //...
      output: {
        clean: true, // 在生成文件之前清空 output 目录
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    output.filename【重要】

    每个输出 bundle 的名称,这些 bundle 将写入到 output.path 选项指定的目录下。

    module.exports = {
      //...
      output: {
        filename: 'bundle.js',
      },
    };
    
    module.exports = {
      //...
      output: {
        filename: '[name].bundle.js',
      },
    };
    
    module.exports = {
      //...
      output: {
        filename: (pathData) => {
          return pathData.chunk.name === 'main' ? '[name].js' : '[name]/[name].js';
        },
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    id Chunk 的唯一标识,从0开始
    name Chunk 的名称
    hash Chunk 的唯一标识的 Hash 值
    chunkhash Chunk 内容的 Hash 值
    其中 hash 和 chunkhash 的长度是可指定的,[hash:8] 代表取8位 Hash 值,默认是20位。

    output.globalObject

    默认:string = 'self'
    当输出为 library 时,尤其是当 libraryTarget 为 'umd’时,此选项将决定使用哪个全局对象来挂载 library。

    module.exports = {
      // ...
      output: {
        library: 'myLib',
        libraryTarget: 'umd',
        filename: 'myLib.js',
        globalObject: 'this',
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    output.library【重要】

    输出一个库,为你的入口做导出。

    module.exports = {
      // …
      entry: './src/index.js',
      output: {
        library: 'MyLibrary',
      },
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    举例:
    使用上面的配置打包该文件

    export function hello(name) {
      console.log(`hello ${name}`);
    }
    
    • 1
    • 2
    • 3

    打包后可以这样使用

    <script src="https://example.org/path/to/my-library.js"></script>
    <script>
      MyLibrary.hello('webpack');
    </script>
    
    • 1
    • 2
    • 3
    • 4

    output.library.name

    同上

    output.library.type【重要】

    配置库暴露的方式。
    类型默认包括 ‘var’、 ‘module’、 ‘assign’、 ‘assign-properties’、 ‘this’、 ‘window’、 ‘self’、 ‘global’、 ‘commonjs’、 ‘commonjs2’、 ‘commonjs-module’、 ‘commonjs-static’、 ‘amd’、 ‘amd-require’、 ‘umd’、 ‘umd2’、 ‘jsonp’ 以及 ‘system’,除此之外也可以通过插件添加。

    举例

    module.exports = {
      // …
      output: {
        library: {
          name: 'MyLibrary',
          type: 'var',
        },
      },
    };
    
    var MyLibrary = _entry_return_;
    // 在加载了 `MyLibrary` 的单独脚本中
    MyLibrary.doSomething();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    module.exports = {
      // …
      output: {
        library: {
          name: 'MyLibrary',
          type: 'assign',
        },
      },
    };
    
    // 直接赋值给MyLibrary,不管有没有定义,慎用
    MyLibrary = _entry_return_;
    // 使用assign-properties更安全:如果 MyLibrary 已经存在的话,它将被重用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    module.exports = {
      // …
      output: {
        library: {
          name: 'MyLibrary',
          type: 'this',
        },
      },
    };
    
    this['MyLibrary'] = _entry_return_;
    
    // 在一个单独的脚本中
    this.MyLibrary.doSomething();
    MyLibrary.doSomething(); // 如果 `this` 为 window 对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    module.exports = {
      // …
      output: {
        library: {
          name: 'MyLibrary',
          type: 'window',
        },
      },
    };
    
    window['MyLibrary'] = _entry_return_;
    
    window.MyLibrary.doSomething();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    module.exports = {
      // …
      output: {
        library: {
          name: 'MyLibrary',
          type: 'global',
        },
      },
    };
    
    global['MyLibrary'] = _entry_return_;
    
    global.MyLibrary.doSomething();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    module.exports = {
      // …
      output: {
        library: {
          name: 'MyLibrary',
          type: 'commonjs',
        },
      },
    };
    
    exports['MyLibrary'] = _entry_return_;
    
    require('MyLibrary').doSomething();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    module.exports = {
      // …
      // 试验性质的模块要加上这个
      experiments: {
        outputModule: true,
      },
      output: {
        library: {
          // do not specify a `name` here,输出 ES 模块。
          type: 'module',
        },
      },
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    module.exports = {
      // …
      output: {
        library: {
          // note there's no `name` here
          type: 'commonjs2',
        },
      },
    };
    
    module.exports = _entry_return_;
    
    require('MyLibrary').doSomething();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    ...还有amd、umd,用的都比较少,我们正常一般不指定,所以我们导出的库支持所有导入方式,或者我们使用esmodule也不错
    
    • 1

    output.library.auxiliaryComment

    给打包后的文件中不同的导出方式做注释

    module.exports = {
      // …
      mode: 'development',
      output: {
        library: {
          name: 'MyLibrary',
          type: 'umd',
          auxiliaryComment: {
            root: 'Root Comment',
            commonjs: 'CommonJS Comment',
            commonjs2: 'CommonJS2 Comment',
            amd: 'AMD Comment',
          },
        },
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    output.path【重要】

    默认 string = path.join(process.cwd(), 'dist')

    const path = require('path');
    
    module.exports = {
      //...
      output: {
        path: path.resolve(__dirname, 'dist/assets'),
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    output.publicPath

    一般用根目录的 publicPath

    module.exports = {
      //...
      output: {
        // One of the below
        publicPath: 'auto', // It automatically determines the public path from either `import.meta.url`, `document.currentScript`, `