当我的用户导航我的SPA时,如何使用Webpack创建可能会或可能不会立即加载的独立SPA套件?
我有一个联系人模块和一个任务模块。两者都有两个依赖关系。我希望WebPack为每个(如果需要)加载时加载的包创建包。
代码如下。问题似乎是这些条目中的每一个都被视为应用程序入口点,因此将webpack引导代码插入其中。
我已经看过CommonsChunkPlugin
的各种示例,但我无法找到它的API参考/文档,而且我可以推测,这不是我想要的。
修改 - 找到了这些文档here,并在我的编辑中添加了下面的插件尝试。
当前配置
module.exports = {
entry: {
contacts: './contacts',
tasks: './tasks'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
}
};
Contacts.js
define(['./ca', './cb'], function(ca, cb){
var name = 'Contacts';
alert(ca + ' ' + cb);
});
Tasks.js
define(['./ta', './tb'], function(ta, tb){
var name = 'TASKS Main';
alert(ta + ' ' + tb);
});
任务-bundle.js
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(3), __webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function(ta, tb){
var name = 'TASKS Main';
alert(ta + ' ' + tb);
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
/***/ },
/* 1 */,
/* 2 */,
/* 3 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){
var name = 'TASKS - A';
alert('ta');
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
/***/ },
/* 4 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){
var name = 'TASKS - B';
alert('tb');
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
/***/ }
/******/ ]);
修改
这是我使用CommonsChunkPlugin尝试的第2号。我创建了一个虚拟的app.js
app.js
var module = window.location.hash.split('/')[0];
alert(module);
然后我将所有联系人和任务文件移动到组件文件夹下,否则将它们单独留下。我的新配置:
module.exports = {
entry: {
app: './app'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: './components/contacts',
filename: 'contacts-component-bundle.js'
}),
new webpack.optimize.CommonsChunkPlugin({
name: './components/tasks',
filename: 'tasks-component-bundle.js'
})
]
};
很奇怪,现在 app-bundle.js 似乎没有任何Webpack引导代码
webpackJsonp([0,1,2],[
/* 0 */
/***/ function(module, exports) {
var module = window.location.hash.split('/')[0];
alert(module);
/***/ }
]);
contacts-components-bundle.js 现在只有这个
webpackJsonp([1,2],[]);
和 tasks-components-bundle.js 似乎包含了我的所有webpack引导代码
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, callbacks = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId])
/******/ callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/ while(callbacks.length)
/******/ callbacks.shift().call(null, __webpack_require__);
/******/ if(moreModules[0]) {
/******/ installedModules[0] = 0;
/******/ return __webpack_require__(0);
/******/ }
/******/ };
/******/ // The module cache
/******/ var installedModules = {};
/******/ // object to store loaded and loading chunks
/******/ // "0" means "already loaded"
/******/ // Array means "loading", array contains callbacks
/******/ var installedChunks = {
/******/ 2:0,
/******/ 1:0
/******/ };
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/ // "0" is the signal for "already loaded"
/******/ if(installedChunks[chunkId] === 0)
/******/ return callback.call(null, __webpack_require__);
/******/ // an array means "currently loading".
/******/ if(installedChunks[chunkId] !== undefined) {
/******/ installedChunks[chunkId].push(callback);
/******/ } else {
/******/ // start chunk loading
/******/ installedChunks[chunkId] = [callback];
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = 'text/javascript';
/******/ script.charset = 'utf-8';
/******/ script.async = true;
/******/ script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"app","1":"./components/contacts"}[chunkId]||chunkId) + "-bundle.js";
/******/ head.appendChild(script);
/******/ }
/******/ };
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ })
/************************************************************************/
/******/ ([]);
同样,我只是尝试使用Webpack来获取SPA概念证明并运行,使用某种根app.js入口点,然后加载一些任意数量的模块/组件需求。这对于requirejs来说非常简单,所以我不得不想象我在这里遗漏了一些关键词,尤其是我所见过的所有文章都在谈论Webpack对于SPA的好处。
编辑2
根据下面的回答,我尝试了以下方法:
app.js
var mod = window.location.hash.split('/')[0];
alert(mod);
require.ensure([], function() {
require('./components/' + mod).show();
});
webpack.config.js
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: {
app: './app'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
}
};
然后在我的构建文件夹中,我留下了app-bundle.js,其中包含我的所有引导代码和我的app.js代码,然后是1.1-bundle.js,其中全部强大的>我的任务和联系人代码。
我也试过这个
module.exports = {
entry: {
app: './app'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: './components/contacts',
filename: 'contacts-component-bundle.js',
children: true
}),
new webpack.optimize.CommonsChunkPlugin({
name: './components/tasks',
filename: 'tasks-component-bundle.js',
children: true
})
]
};
其结果与上述相同,但现在还有tasks-component-bundle.js和contacts-component-bundle.js,两者都只有 一些webpack引导代码;任务和联系人代码仍然是1.1-bundle。
同样,我只是希望能够以某种方式告诉Webpack将各个模块及其依赖项捆绑在一起,以便在需要时进行后续的延迟异步加载。
最终的答案由Tobias-Webpack创建者提供,我将在这里为后人提供。
真正的动态是不可能的。 webpack(在require.js中的构造)在执行之前编译您的应用程序,并且无法访问运行时信息。只要您的动态表达不包含,动态就需要webpack潜入每个可能的文件夹......您甚至应该能够将其配置为使用mod +&#39; /&#39; + mod使用ContextReplacementPlugin和一点RegExp魔术(在RegExp中使用反向引用)。默认情况下,它将包含太多模块。
答案 0 :(得分:13)
webpack为每个异步require语句(require.ensure
或AMD require([])
)创建一个拆分点。因此,您需要为应用中的每个延迟加载部分编写require([])
。
您的SPA只有一个入口点:(客户端)路由器。我们称之为app.js
。这些页面是按需加载的,也是一个入口点。
webpack.config.js:
module.exports = {
entry: {
app: './app'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
}
}
app.js:
var mod = window.location.hash.split('/')[0].toLowerCase();
alert(mod);
switch(mod) {
case "contacts":
require(["./pages/contacts"], function(page) {
// do something with "page"
});
break;
case "tasks":
require(["./pages/tasks"], function(page) {
// do something with "page"
});
break;
}
替代方案:使用&#34;上下文&#34;。
使用动态依赖时i。 e require("./pages/" + mod)
您无法为每个文件写一个分割点。对于这种情况,有一个加载器包装require.ensure
块中的文件:
app.js
var mod = window.location.hash.split('/')[0].toLowerCase();
alert(mod);
require("bundle!./pages/" + mod)(function(page) {
// do something with "page"
});
这是特定于webpack的。别忘了npm install bundle-loader --save
。检查正确的外壳,它区分大小写。
答案 1 :(得分:4)
我已经完成了一些工作,并希望在这里发布我的工作,以造福他人。
前提是一个由单个页面组成的Web应用程序,最初加载了某些框架实用程序,当用户导航并更改url哈希时,应用程序的所有后续部分都会按需加载。
概念验证app.js框架/入口点看起来像这样
<强> app.js 强>
var framework = require('./framework/frameworkLoader');
window.onhashchange = hashChanged;
hashChanged(); //handle initial hash
function hashChanged() {
var mod = window.location.hash.split('/')[0].replace('#', '');
if (!mod) return;
framework.loadModule(mod, moduleLoaded, invalidModule);
function moduleLoaded(moduleClass, moduleHtml){
//empty to remove handlers previously added
$('#mainContent').empty();
$('#mainContent').html(moduleHtml);
var inst = new moduleClass();
inst.initialize();
}
function invalidModule(){
alert('Yo - invalid module');
}
};
显然,兴趣点是framework.loadModule(mod, moduleLoaded, invalidModule);
。正如Tobias所说,必须有单独的,独立的AMD风格的需求声明(我相信它有一个CommonJS替代品,但我还没有探索过),因为它有可能。显然没有人会想要为大型应用程序写出每种可能性,所以我的假设是,某些简单的节点任务将作为构建过程的一部分存在,以导航应用程序的结构,并自动生成所有这些需要为每个模块声明。在这种情况下,假设modules
中的每个文件夹都包含一个模块,主要代码和html是以同名文件命名的。例如,对于联系人,模块定义将位于modules / contacts / contacts.js中,html位于modules / contacts / contacts.htm中。
我只是手动写出了这个文件,因为有Node导航文件夹和文件结构,输出新文件非常简单。
<强> frameworkLoader.js 强>
//************** in real life this file would be auto-generated*******************
function loadModule(modName, cb, onErr){
if (modName == 'contacts') require(['../modules/contacts/contacts', 'html!../modules/contacts/contacts.htm'], cb);
else if (modName == 'tasks') require(['../modules/tasks/tasks', 'html!../modules/tasks/tasks.htm'], cb);
else onErr();
}
module.exports = {
loadModule: loadModule
};
使用其余文件:
<强> webpack.config.js 强>
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: {
app: './app'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js',
publicPath: '/build/',
}
};
主要的html文件
<强>的default.htm 强>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="build/app-bundle.js"></script>
</head>
<body>
<h1>Hello there!</h1>
<h2>Sub heading</h2>
<h3>Module content below</h3>
<div id="mainContent"></div>
</body>
</html>
下一步是向这些模块添加临时依赖项。不幸的是,添加require(['dep1', 'dep2'], function(){
并不像我希望的那样完成工作;急切地追查列表中的所有依赖项,并将它们全部捆绑在有问题的模块中,而不是按需加载它们。这意味着如果联系人和任务模块都需要相同的依赖关系(因为他们将要执行)两个模块都捆绑了整个依赖关系,导致它在用户浏览联系人然后任务时加载和重新加载
解决方案是捆绑加载器npm install bundle-loader --save
。这允许我们执行require('bundle!../../libs/alt')
,它返回函数,在调用时获取我们的依赖项。该函数将一个回调作为参数,该回调接受我们新加载的依赖项。显然加载这样的N个依赖项将需要令人不快的代码来纠缠N个回调,因此我将在片刻内构建Promise支持。但首先要更新模块结构以支持依赖项规范。
<强> contacts.js 强>
function ContactsModule(){
this.initialize = function(alt, makeFinalStore){
//use module
};
}
module.exports = {
module: ContactsModule,
deps: [require('bundle!../../libs/alt'), require('bundle!alt/utils/makeFinalStore')]
};
<强> tasks.js 强>
function TasksModule(){
this.initialize = function(alt){
//use module
};
}
module.exports = {
module: TasksModule,
deps: [require('bundle!../../libs/alt')]
};
现在每个模块都返回一个包含模块本身的对象文字,以及它所需的依赖项。显然,只写出一个字符串列表会很好,但是我们需要require('bundle!
调用,所以Webpack可以看到我们需要的东西。
现在为我们的主app.js构建Promise支持
<强> app.js 强>
var framework = require('./framework/frameworkLoader');
window.onhashchange = hashChanged;
hashChanged(); //handle initial hash
function hashChanged() {
var mod = window.location.hash.split('/')[0].replace('#', '');
if (!mod) return;
framework.loadModule(mod, moduleLoaded, invalidModule);
function moduleLoaded(modulePacket, moduleHtml){
var ModuleClass = modulePacket.module,
moduleDeps = modulePacket.deps;
//empty to remove handlers previous module may have added
$('#mainContent').empty();
$('#mainContent').html(moduleHtml);
Promise.all(moduleDeps.map(projectBundleToPromise)).then(function(deps){
var inst = new ModuleClass();
inst.initialize.apply(inst, deps);
});
function projectBundleToPromise(bundle){
return new Promise(function(resolve){ bundle(resolve); });
}
}
function invalidModule(){
alert('Yo - invalid module');
}
};
这会导致为contacts,tasks,alt和makeFinalStore创建单独的单个bundle文件。加载任务首先显示包含任务模块的包,并在网络选项卡中显示带有alt加载的包;之后加载联系人显示联系人捆绑加载与makeFinalStore捆绑。加载联系人首先显示联系人,alt和makeFinalStore包加载;之后加载任务只显示任务捆绑加载。
最后,我想扩展contacts模块,以便它支持自己的ad hoc动态加载。在现实生活中,联系人模块可以即时加载联系人的账单信息,联系信息,订阅信息等。显然,这种概念证明将更加简单,接近愚蠢。
在contacts文件夹下,我创建了一个contactDynamic文件夹,其中包含以下文件
contentA.js
contentA.htm
contentB.js
contentB.htm
contentC.js
contentC.htm
<强> contentA.js 强>
module.exports = {
selector: '.aSel',
onClick: function(){ alert('Hello from A') }
};
<强> contentA.htm 强>
<h1>Content A</h1>
<a class="aSel">Click me for a message</a>
<强> contentB.js 强>
module.exports = {
selector: '.bSel',
onClick: function(){ alert('Hello from B') }
};
<强> contentB.htm 强>
<h1>Content B</h1>
<a class="bSel">Click me for a message</a>
<强> contentC.js 强>
module.exports = {
selector: '.cSel',
onClick: function(){ alert('Hello from C') }
};
<强> contentC.htm 强>
<h1>Content C</h1>
<a class="cSel">Click me for a message</a>
contacts.js的更新代码如下。有些事情需要注意。我们提前构建动态上下文,以便我们可以适当地排除文件。如果我们不这样做,那么bundle!
的动态需求会在到达html文件时失败;我们的上下文将文件限制为*.js
。我们还为.htm文件创建了一个上下文 - 请注意我们同时使用bundle!
和html!
个加载器。另请注意,订单重要 - bundle!html!
有效但html!bundle!
导致这些捆绑包无法构建,我希望有人可以就其原因发表评论。但实际上,为每个单独的.js和.htm文件创建了单独的包,并且仅在需要时按需加载。当然,我像以前一样在Promises中包装bundle!
次调用。
此外,我了解可以使用ContextReplacementPlugin
代替这些上下文,我希望有人可以告诉我如何:ContextReplacementPlugin
的实例传递给动态{{ 1}}?
<强> contacts.js 强>
require
<强> contacts.htm 强>
function ContactsModule(){
this.initialize = function(alt, makeFinalStore){
$('#contacts-content-loader').on('click', '.loader', function(){
loadDynamicContactContent($(this).data('name'));
});
};
}
function loadDynamicContactContent(name){
var reqJs = require.context('bundle!./contactDynamic', false, /.js$/);
var reqHtml = require.context('bundle!html!./contactDynamic', false, /.htm$/);
var deps = [reqJs('./' + name + '.js'), reqHtml('./' + name + '.htm')];
Promise.all(deps.map(projectBundleToPromise)).then(function(deps){
var code = deps[0],
html = deps[1];
$('#dynamicPane').empty().html(html);
$('#dynamicPane').off().on('click', code.selector, function(){
code.onClick();
});
});
}
function projectBundleToPromise(bundle){
return new Promise(function(resolve){ bundle(resolve); });
}
module.exports = {
module: ContactsModule,
deps: [require('bundle!../../libs/alt'), require('bundle!alt/utils/makeFinalStore')]
};
最后,最后的default.htm
<强>的default.htm 强>
<h1>CONTACTS MODULE</h1>
<div id="contacts-content-loader">
<a class="loader" data-name="contentA">Load A</a>
<a class="loader" data-name="contentB">Load B</a>
<a class="loader" data-name="contentC">Load C</a>
</div>
<div id="dynamicPane">
Nothing loaded yet
</div>
答案 2 :(得分:0)
我认为require.ensure可能是关键所在。这将允许您设置动态加载的分割点。您可以通过某种方式将其与SPA的路由器连接起来。以下是Pete Hunt的基本想法:https://github.com/petehunt/webpack-howto#9-async-loading。