Webpack 代码分割

7

我正在尝试使用webpack设置我的项目,我已经了解了代码分割,现在我想要创建两个独立的包,一个用于应用程序代码,另一个用于库和框架。因此,我的webpack配置如下:

entry: {
    app: './app/index.js',
    vendor: './app/vendor.js'
},
output: {
    filename: '[name].[chunkhash].js',
    path: path.resolve(__dirname, 'public/js')
},

watch: true,

module: {
    rules: [{
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
            use: 'css-loader'
        })
    }]
},

plugins: [
    new ExtractTextPlugin('styles.css'),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor'
    })
]

在我的vendor.js包中,我只有一行代码:

 import moment from 'moment';

当我尝试在我的app.js文件中使用它时,它告诉我moment未定义。所以,我不明白的是,包是否具有公共作用域?如果没有,那么我如何访问在另一个包中导出的变量?如果不能,那么像这里描述的https://webpack.js.org/guides/code-splitting-libraries/,拥有供应商包的意义是什么?

moment jsnode_modules 中吗?你如何在 index.js 中使用 vendor.js - Tomas Ramirez Sarduy
是的,它在 node_modules 中,否则webpack在构建时会输出错误。我在 index.html 文件中包含 vendor.js 然后是 app.js。就像我说的,vendor js 只有一行导入 moment,而 index.js 有一行控制台日志输出(moment())。 - FancyNancy
你需要引入 vendor.js,参见 http://stackoverflow.com/questions/31433772/sharing-variables-through-files-with-webpack - Tomas Ramirez Sarduy
如果您正在寻找Webpack 4+的解决方案,那么也许这篇简短的指南可以为您提供一切所需,以实现JavaScript库的代码拆分。 - Robin Wieruch
3个回答

7
这些打包文件没有共享作用域。实际上,Webpack 尊重每个模块的作用域,就像 Node.js 一样,因此,即使在同一个打包文件中,您也不能使用来自另一个模块的任何内容,除非您导入它。
您需要在使用 moment 的每个模块中都导入它。这并不意味着您需要多次包含其源代码。Webpack 只包含一次源代码,每次导入都会引用它。
在这种情况下,代码分割(CommonsChunkPlugin)只是将源代码放入供应商打包文件中,跨打包文件的每个导入都将引用供应商打包文件。这意味着您不需要将供应商依赖项与应用程序打包文件一起发布,因此浏览器可以缓存供应商打包文件。当您发布新版本的应用程序时,如果未更改供应商打包文件,则浏览器只需要下载应用程序打包文件,因为它已经具有正确的供应商打包文件。
让我们考虑这个非常简短的示例应用程序:
import moment from 'moment';
console.log(moment().format());

没有使用CommonsChunkPlugin插件,生成的包(未压缩)为:
vendor.js  470 kB       0  [emitted]  [big]  vendor
   app.js  470 kB       1  [emitted]  [big]  app

这个文件大小为470KB,因为它在捆绑包中包含了整个moment源代码,并且更糟糕的是,另一个使用moment的捆绑包也包含了整个源代码。虽然不应该在此处使用供应商,但考虑到可能需要使用它的另一个捆绑包。当您更改应用程序中的某些内容时,用户将不得不再次下载整个470KB。

使用CommonsChunkPlugin

   app.js  504 bytes       0  [emitted]         app
vendor.js     473 kB       1  [emitted]  [big]  vendor

现在该应用程序已经缩小到504字节。当您更改应用程序时,用户只需下载此小捆绑包(假设vendor.js已被缓存)。这也意味着任何使用moment的其他捆绑包都将引用vendor.js,而不是在捆绑包中包含源代码。 vendor.js的大小略有增加,因为webpack需要一些额外的代码来处理来自另一个捆绑包的导入。这还要求在加载app.js之前加载vendor.js
出于简洁起见,我省略了文件名中的哈希值,但这些哈希值对于缓存破坏是必需的。有关更多信息,请参见Caching

所以,我并没有按照代码架构进行拆分,因为我需要在我的index.js文件中进行所有的导入,尽管我已经在vendor.js中完成了它? - FancyNancy
顺便说一下,我刚试着用Angular做了同样的事情,它似乎正常工作,就像我认为它应该工作一样。我已经在vendor.js中导入了Angular,并且以某种方式在index.js中可用。为什么这不能用于moment? - FancyNancy
1
每个模块都应该能够独立运行,依赖于全局变量存在通常是有问题的,只有传统模块(例如jQuery插件)才会这样做。一些模块可能会将某些内容公开为全局变量,但这也是同样的情况。有关更多详细信息,请参见Shimming。在JavaScript中,模块是必不可少的,不再需要将所有内容公开为全局变量的时代已经过去了。 - Michael Jungo

4
我看到了问题,它与代码拆分无关。根据您的具体情况,有不同的方法可以实现这一点:

index.js中引用vendor.js

vendor.js

export default moment from 'moment';

index.js

var moment = require('vendor.js');
console.log(moment());

使用导入装载器:

导入装载器允许您使用依赖于特定全局变量的模块。对于依赖于全局变量如$或将其视为窗口对象的第三方模块,这非常有用。导入装载器可以添加必要的require('whatever')调用,以便这些模块与webpack一起工作。

require("imports-loader?$=moment,angular!./index.js");

通过插件:

您可以通过插件将momentJS暴露给window对象,这样您就可以在index.js中通过window.moment访问。

new webpack.ProvidePlugin({
    "window.moment": "moment"
}),

0

所以我一直为了从一个单独的.js文件中导入一个对象及其所有方法到app.js而苦恼。事实证明,你只需要做的是...

假设你在ourjsfile.js中定义了这个var xys{};对象

然后你定义了各种方法,比如...

xyz.add = function(){ ... };

在文件末尾,你只需要添加...
module.exports = xyz;

就是这样!现在为了在app.js中使用这个对象(变量),你需要像下面这样require它...

var xyz = require('./ourjsfile');

看哪!!!对象已经被导入。这个问题让我为一个如此简单的解决方案而苦恼了很长时间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接