在编写库的时候,我们有时候会希望按需加载某些依赖,例如如果代码的运行环境不支持某些功能的话,就加载相关的Polyfill。webpack作为当前最流行的打包工具,早已支持动态加载的功能了。本文将讨论一种用webpack打包含动态加载的类库的方法。
注意,本文是写给类库作者看的,如果读者写的是应用,那就没有必要往下看了。
// my-lib.js
class MyLib {
loadDeps() {
return new Promise((resolve, reject) => {
if (global.TextDecoder === undefined) {
return Promise.all([
import('./deps/text-encoding'),
import('./deps/other-dep.js'),
import('./deps/another-dep.js'),
]).then(resolve).catch(reject);
} else {
resolve();
}
});
}
initialize() {
this.decoder = new TextDecoder();
// ...
}
}
这个类库中有个loadDeps方法,会根据运行环境检测TextDecoder是否存在,如果不存在就会去加载依赖,这些依赖是在本地目录中。
当我们把类库发布到npm之后,我们就会这么使用它:
// app.js
import MyLib from 'my-lib';
class App {
constructor() {
this.myLib = new MyLib();
}
}
const app = new App();
app.myLib.loadDeps().then(() => {
app.myLib.initialize();
console.log(app.myLib.decoder);
});
这样,我们的类库无论在什么环境都能使用到TextDecoder了。但,真的会这么顺利吗?
当我们发布到npm仓库之前,我们会先用Webpack构建项目,这时候webpack就会分析文件中的依赖路径,把依赖文件生成到output.path中,同时import()方法就已经变成webpack内部的方法了,依赖的路径也变成output.publicPath相关的了。假设我们的webpack配置是这样的:
// webpack.config.js
module.exports = {
output: {
filename: 'index.js',
chunkFilename: '[name].js',
path: 'dist/',
publicPath: '/',
libraryTarget: 'umd'
},
// ...
};
然后输出了这些文件:
// webpack output
Built at: 02/19/2019 5:08:41 PM
Asset Size Chunks Chunk Names
1.js 79 bytes 1 [emitted]
2.js 79 bytes 2 [emitted]
3.js 79 bytes 3 [emitted]
index.js 144 KiB 0 [emitted] main
Entrypoint main = index.js
那么当应用层调用app.myLib.loadDeps()的时候会加载依赖的路径会是怎样的呢?猜对了,浏览器会尝试加载这些路径:
/1.js
/2.js
/3.js
但是结果呢?当然是404了。因为网站的根目录没有这些文件呀,除非你把这些文件复制到网站根目录。同样道理,如果publicPath是相对路径的话(假设是”),那么请求依赖的路径就会相对于当前的URL了,即如果当前URL是/topics/:uid,那请求的路径就会是/topics/:uid/1.js。除非当前目录下有这些依赖,不然结果还是404。
当然,我们还可以修改服务端配置,把/1.js和**/1.js都指向/path/to/project/node_modules/my-lib/dist/1.js。
对应用层来说,为了一个库而改服务器配置就显得太麻烦了。
对一个类库来说,它应该管理好自己的依赖,不应该让应用层甚至是服务器端来配合。
我们需要解决这个路径问题,既要保证结果正确,又要方便开发。很明显,这个问题是因为webpack打包的时候处理了import()导致的,如果我们不在编译时处理,而是运行时处理,这不就可以达到目的了吗?
先来准备一下 dist/ 的目录结构,修改webpack的配置:
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
output: { ... },
plugins: [
new CopyWebpackPlugin([{
from: 'src/deps/',
to: '.'
}])
]
}
CopyWebpackPlugin会把 src/deps/ 中的文件复制到 dist/ 目录中,这时候dist/就会变成:
dist
├── 1.js
├── 2.js
├── 3.js
├── text-encoding.js
├── other-dep.js
├── another-dep.js
└── index.js
下一步就是让webpack不解析import()了,这里有两种做法:
这个方案很容易实现,只需要让应用层来调用import()就可以了。
// my-lib.js
class MyLib {
constructor(options) {
this.onLoadDeps = options.onLoadDeps || null;
}
loadDeps() {
return new Promise((resolve, reject) => {
if (global.TextDecoder === undefined) {
if (this.onLoadDeps) {
this.onLoadDeps().then(resolve).catch(reject);
} else {
Promise.all([
import('./deps/text-encoding'),
import('./deps/other-dep.js'),
import('./deps/another-dep.js'),
]).then(resolve).catch(reject);
}
} else {
resolve();
}
});
}
}
// app.js
import MyLib from 'my-lib';
class App {
constructor() {
this.myLib = new MyLib({
onLoadDeps: () => Promise.all([
import('my-lib/dist/text-encoding'),
import('my-lib/dist/other-dep.js'),
import('my-lib/dist/another-dep.js'),
]);
});
}
}
这种做法非常简单,而且在开发环境下也不需要任何处理就能正常运行了。
不足之处是如果有多个项目都引用了这个类库的话,那么当类库添加了新的依赖时,所有引用了这个类库的项目都要改动源码。这会是一个比较繁琐的事情。
对于这个解决方案,其实还有一个变种,相对来说更加方便:
// my-lib.js
class MyLib {
constructor(options) {
this.importPrefix = options.importPrefix || './deps';
}
loadDeps() {
return new Promise((resolve, reject) => {
if (global.TextDecoder === undefined) {
return Promise.all([
import(this.importPrefix + '/text-encoding'),
import(this.importPrefix + '/other-dep.js'),
import(this.importPrefix + '/another-dep.js'),
]).then(resolve).catch(reject);
} else {
resolve();
}
});
}
}
// app.js
import MyLib from 'my-lib';
class App {
constructor() {
this.myLib = new MyLib({ importPrefix: 'my-lib/dist' });
}
}
注意:这个变种方案有可能会出现 Critical dependency: the request of a dependency is an expression 的报错。
这个方案稍微复杂一点点。
想要webpack不处理import(),那么就不能让webpack去解析含有import()的文件,即需要把含有加载依赖的部分分离到另一个文件中。
// runtime.js
module.exports = {
onLoadDeps: function() {
return Promise.all([
import('my-lib/dist/text-encoding'),
import('my-lib/dist/other-dep.js'),
import('my-lib/dist/another-dep.js'),
]);
}
}
注意:因为 webpack 不会解析这个文件,loader 就不会处理这个文件,所以这个文件里面最好使用 Node.js 原生支持的语法。
// my-lib.js
import RUNTIME from './runtime';
class MyLib {
loadDeps() {
return new Promise((resolve, reject) => {
if (global.TextDecoder === undefined) {
RUNTIME.onLoadDeps().then(resolve).catch(reject);
} else {
resolve();
}
});
}
}
然后修改webpack配置:
module.exports = {
output: { ... },
module: {
noParse: /src\/runtime/,
},
plugins: [ ... ]
}
这样,webpack在处理my-lib.js的时候会把runtime.js加载进来,但不会去解析它。所以会得到以下的结果:
// dist/index.js
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = {
onLoadDeps: function onLoadDeps() {
return Promise.all([import('my-lib/dist/text-encoding'), import('my-lib/dist/other-dep.js'), import('my-lib/dist/another-dep.js')]);
}
};
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
// ...
var MyLib =
/*#__PURE__*/
function () {
function MyLib() {}
var _proto = MyLib.prototype;
_proto.loadDeps = function loadDeps() {
var _this = this;
return new Promise(function (resolve, reject) {
if (global.TextDecoder === undefined) {
_runtime__WEBPACK_IMPORTED_MODULE_0___default.a.onLoadDeps().then(resolve).catch(reject);
} else {
resolve();
}
});
};
_proto.initialize = function initialize() {
this.decoder = new TextDecoder(); // ...
};
return MyLib;
}();
// ...
如果应用层引用了这个类库,那么webpack打包应用的时候就会处理类库中的import(),这样就和应用层平时的动态加载一样了,上面的问题也就解决了。
最后剩下一个问题,那就是在开发环境下,我们也需要测试runtime.js,但这时候它是import(‘my-lib/dist/xxx’)的,这个肯定会报Error:Cannotfindmodule的错误。
这时候可以像方案一那样,用import(importPrefix+’/text-encoding’)的方式来解决,也可以利用NormalModuleReplacementPlugin来解决。
// webpack.dev.js
module.exports = {
// ...
plugins: [
new webpack.NormalModuleReplacementPlugin(/my-lib\/dist\/(.*)/, function (resource) {
resource.request = resource.request.replace(/my-lib\/dist/, '../src/deps')
}),
]
}
这个插件可以改变重定向资源,上面的配置是把my-lib/dist/*里面的资源都重定向到../src/deps。
更多详细的用法,可以参考下官方文档NormalModuleReplacementPlugin。
注意:这个插件最好只用在开发环境中。
这个方案虽然有些繁琐,但最大的优点是可以把依赖管理都交给类库自己处理,不需要应用层干预。就算类库日后改变打包位置(不再是dist/)也无需让应用层知道。
作者:scarletsky
scarletsky.github.io/2019/02/19/webpack-bundling-libraries-with-dynamic-imports/
现在的打包Vue项目目前流行的就是使用weex和cordova。weex是阿里提供并且Vue的作者也极力推荐的,有兴趣的可以去学习使用一下。下面说说怎么使用cordova打包Vue项目
随着ES的普及我们越来越多的开始使用ES6的语法了,当然也随着mvvm框架的流行少不了js模块化,那js模块化又有那些呢?在很早的时候大家都用的命名空间,现在也有人用(库名.类别名.方法名)
webpack在打包的时候第一次总是会做很长的准备工作,包括加载插件之类的。在刚接触webpack的时候总是webpack一下-测一下-改一下-再webpack一下,这种方式最后让很多人崩溃了觉得webpack一点都不好用
在新建好的项目中,一般执行npm run build就是打包了,但此时只能打包到一个环境,不同环境需要配置不同的地址,可以手动更改接口的地址,也可以自行配置命令而不需要每次打包进行地址切换
打包工具可以更好的管理html,css,javascript,使用可以锦上添花,不使用也没关系。html好比是房子的地基,css和 javascript是房子的建筑材料,这三个部分一起组成个漂亮的房子
准备一个空文件夹,然后执行下列命令:然后创建一个 dist 目录,并在里面创建一个 indedx.html:接着创建一个 src 目录,在里面创建一个 lib 文件夹,创建一个 until.js:再创建 components 文件夹,再写入几个 js:
我们有时候因为一些特殊需求,可能需要将js/css/img等资源文件都打包到根路径下,但vue-cli3.0的路径配置中仅有assetsDir配置项能够配置所有的静态文件所在的文件夹,并不能针对css/js/img等资源文件分别来做设置,那么请看我如何尝试的吧!
前端经过漫长的发展,涌现出了很多实践方法来处理复杂的工作流程,让开发变得更加简便,其中,模块化可以使复杂的程序细化成为各个小的文件,而webpack并不强制你使用某种模块化方案,而是通过兼容所有模块化方案让你无痛接入项目
最近,遇到复杂h5页面开发,为了优化H5首屏加载速度,想到使用按需加载的方式,减少首次加载的JavaScript文件体积,于是将处理过程在这里记录一下,涉及到的主要是以下三点:
webpack在build包的时候,有时候会遇到打包时间很长的问题,这里提供了一个解决方案,让打包如丝般顺滑~在用 Webpack 打包的时候,对于一些不经常更新的第三方库
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!