前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端模块化方案:前端模块化/插件化异步加载方案探索

前端模块化方案:前端模块化/插件化异步加载方案探索

原创
作者头像
周陆军博客
发布2023-05-14 22:40:01
1.3K0
发布2023-05-14 22:40:01
举报
文章被收录于专栏:前端博客前端博客

前端模块化序篇

这里建议先复习一下《再唠叨JS模块化加载之CommonJS、AMD、CMD、ES6

  • AMD: define + require
  • CMD: exports + require
  • ES6: export + import

之前由于由于ES6本身是原生语言支持实现的模块化,但是现代浏览器大多都还未支持,因此必须使用相应的transpiler工具转换成ES5的AMD,CMD模块,再借助于systemjs/requirejs等模块加载工具才能使用。

前端的模块系统经历了长久的演变,对应的模块化方案也几经变迁。

  • JavaScript打包方案从最初简单的文件合并,到AMD 的模块具名化并合并,再到browserify将CommonJS 模块转换成为浏览器端可运行的代码,打包器做的事情越来越复杂,角色也越来越重要,加载器貌似在弱化。
  • Javascript中模块加载器从最初小而简单lab.js/curl.js到RequireJS/sea.js、Browserify、Webpack和SystemJS一直在演进发展。

js语言本身并不支持模块化,同时浏览器中js和服务端nodejs中的js运行环境是不同的,如何实现浏览器中js模块化主流有两种方案:

  1. requirejs/seajs: 是一种在线“编译”模块的方案,相当于在页面上加载一个CommonJS/AMD模块格式解释器。这样浏览器就认识了define, exports,module这些东西,也就实现了模块化。
  2. browserify/webpack:是一个预编译模块打包的方案,相比于第一种方案,这个方案更加智能。由于是预编译的,不需要在浏览器中加载解释器。你在本地直接写JS,不管是AMD/CMD/ES6风格的模块化,它都能认识,并且编译成浏览器认识的JS。

到了2021,以webkit为内核的众多浏览器 都支持了es6 原生加载。本篇再来梳理一下前端模块方案。

ES6异步加载

浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。

代码语言:javascript
复制
<script?type="module"?src="./foo.js"></script>

其实这个并没有什么好书的。我想说的是在代码中异步加载模块。实现cmd的效果。比如:

app/es6-file.js:

代码语言:javascript
复制
export?class?q?{
export?let?counter?=?3;
export?function?incCounter()?{
??counter++;
}

浏览器加载:

代码语言:javascript
复制
<script>
??import?{?counter,?incCounter?}?from?'./lib';
??//?import?{?counter,?incCounter?}?from?'https://www.zhoulujun.cn/demo/lib';?
???console.log(counter);?//?3
??incCounter();
??console.log(counter);?//?4
</script>

ES6模块定义名为export,提供一个静态构造函数访问器。

更多的推荐阅读

es5时代模块加载器

比较代表性的就是require.js/sea.js、Browserify

AMD阵营

超快速AMD入门 (Super Quick AMD Primer)

如果您不熟悉AMD的结构,我将为您提供您所听到的最简单的解释。 AMD是您用来异步定义和要求模块的系统。 定义返回一个或零个对象。 define和require的第一个参数通常是一个依赖项数组。 第二个参数是一个函数; define返回结果,require执行基本的回调:

代码语言:javascript
复制
//?"define"?a?module
define(["namespace/dependencyA",?"namespace/dependencyB"],?function(depA,?depB)?{
	//?Whole?bunch?of?processing
	
	
	//?Return?what?this?module?defines
	return?function()?{
		//?Or?an?object,?or?whatever
	}
});
//?"require"?to?use?modules:
require(["namespace/dependencyC"],?function(depC)?{
	
	//?depC?can?be?used?in?here?only
	//?Yay?for?modularity!	

有数十种AMD JavaScript加载程序可用,其中最受欢迎的是RequireJS。 还有鲜为人知JavaScript加载程序,例如YepNope,script.js,LAB.js和Dojo的新本机加载程序。我最先接触的就是 curl.js,具体查看?https://github.com/cujojs/curl

Require.JS

RequireJS 是一个JavaScript 模块加载器,基于AMD 规范实现

它同时也提供了对模块进行打包与构建的工具r.js,通过将开发时单独的匿名模块具名化并进行合并,实现线上页面资源加载的性能优化。

RequireJS 与r.js 等一起提供的一个模块化构建方案。

Require是出现在2009年,它完全不同于之前的那些懒加载器,它将脚本标签写入到DOM中,监听完成的事件,然后递归加载依赖:

代码语言:javascript
复制
<script?src=“tools/require.js”?data-main=“myAppInit.js”?></script>

...或者如下调用指明的函数名称...

代码语言:javascript
复制
<script?src=“tools/require.js”></script>

再调用

代码语言:javascript
复制
<script>
require([‘myAppInit’,?‘libs/jQuery’],?function?(myApp,?$)?{?...
</script>

上面两个用法不建议同时使用。虽然Require存在各种特殊情况,但是其灵活性和强大性还是支持它成为浏览器端流行的加载器。

更多参看官网:https://requirejs.org/

Browserify

https://browserify.org/

Browserify允许CommonJS格式模块在前端使用,主要用于在浏览器中使用 npm 包,最终会转换为 commonJS (require) 类似方式,在浏览器使用。

它不只是一个模块加载器,而是模块捆绑器(bundler),是一个完整的代码构建段的工具,提供客户端能加载一堆代码的功能。

首先需要node和npm已经安装,获得包:

代码语言:javascript
复制
npm?install?-g?–save-dev?browserify

以CommonaJS格式编写你的模块即可。然后使用下面命令捆绑:

代码语言:javascript
复制
npm?install?-g?–save-dev?browserify

它会递归以此发现entry-point中所有依赖包,然后将它们组装在一个单个文件中:

代码语言:javascript
复制
<script?src=”bundle-name.js”></script>

对于前端,你可以最小化合并核心代码,然后让可选模块在之后需要时加载,这样即节约了带宽也不影响模块编程功能实现。

更多请参看官网:https://browserify.org/

Browserify缺点

基于流 Stream,旧时代产物,尽管也能勉强处理 css(CSS bundlers),html(brfs),但是不太友好,且年久失修

browserify必须把源代码打成bundle然后再引用,就决定了他不能直接调试源代码,这对于程序员是很不友好的。虽然我们可以使用? watchify(可以动态把你写的代码立即编译成bundle) 和 --debug 选项(给编译后的代码加上source maps)。但是依然只是近似于直接调试源代码。

SystemJS

https://github.com/systemjs/

Systemjs是一个可配置模块加载器,为浏览器和NodeJs启用动态的Es模板加载器。任何具有标准的URL都可被加载为一个模块:

代码语言:javascript
复制
<script?src="system.js"></script>
<script>
??//?加载相对于当前地址的url
??SystemJS.import('./local-module.js');
??//?加载绝对url的地址
??SystemJS.import('https://code.jquery.com/jquery.js');
</script>

可以加载任何类型的模块格式,并由SystemJS自动检测。

SystemJS 诞生于 2015 年,那个时候 ES Module 还未成为标准,在浏览器端只能通过 requirejs、seajs 等方案实现模块加载,随着 npm 在前端界的流行,一个项目中可能存在多种模块规范,所以我认为 SystemJS 最初诞生的目的是为了做一个通用的模块加载器,在浏览器端实现对 CommonJS、AMD、UMD 等各种模块的加载。 SystemJS 是(浏览器尚未正式支持importMap) 原生 ES Module 的替代品,ES Module 被编译成 System.register 格式之后能够跑在旧版本的浏览器当中。

在本地运行时,请确保从本地服务器或启用了本地XHR请求的浏览器运行。如果不是,将会收到一条错误消息。

对于Mac上的Chrome,您可以运行它: /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --allow-file-access-from-files &> /dev/null & 在Firefox中,这需要导航到about:config,进入security.fileuri.strict_origin_policy过滤器框并将选项切换为false。

SystemJS加载配置
baseURL

baseURL提供了一种根据一个相对地址装载模块的机制。

这使得能够从许多不同的请求URL访问相同的模块

代码语言:javascript
复制
SystemJS.config({
??//?set?all?requires?to?"lib"?for?library?code
??baseURL:?'/lib/',
???//?set?"app"?as?an?exception?for?our?application?code
????paths:?{
??????'app/*':?'/app/*.js'
????}
});
//?加载?/modules/jquery.js
SystemJS.import('jquery.js');<br>

更多的参看官方文档:https://github.com/systemjs/systemjs

es5时代模块打包方案

Grunt和Gulp属于任务流工具Tast Runner , 而 webpack属于模块打包工具 Bundler。

grunt

https://gruntjs.com/

Grunt 是老牌的构建工具,特点是配置驱动,你需要做的就是了解各种插件的功能,然后把配置整合到 Gruntfile.js 中

代码语言:javascript
复制
module.exports?=?function(grunt)?{
??grunt.initConfig({
????//?js格式检查任务
????jshint:?{
??????files:?['Gruntfile.js',?'src/**/*.js',?'test/**/*.js'],
??????options:?{
????????globals:?{
??????????jQuery:?true
????????}
??????}
????},
????//??代码压缩打包任务
????uglify:?{}
????watch:?{
??????files:?['<%=?jshint.files?%>'],
??????tasks:?['jshint']
????}
??});
??grunt.initConfig({

??});
??//?导入任务插件
??grunt.loadNpmTasks('grunt-contrib-jshint');
??grunt.loadnpmTasks('grunt-contrib-uglify');
??grunt.loadNpmTasks('grunt-contrib-watch');
??//?注册自定义任务,?如果有多个任务可以添加到数组中
??grunt.regusterTask('default',?['jshint'])
};

Grunt 缺点也是配置驱动,当任务非常多的情况下,试图用配置完成所有事简直就是个灾难;再就是它的 I/O 操作也是个弊病,它的每一次任务都需要从磁盘中读取文件,处理完后再写入到磁盘,例如:我想对多个 less 进行预编译、压缩操作,那么 Grunt 的操作就是:

读取 less 文件 -> 编译成 css -> 存储到磁盘 -> 读取 css -> 压缩处理 -> 存储到磁盘

这样一来当资源文件较多,任务较复杂的时候性能就是个问题了。

glup

https://gulpjs.com/

Gulp是后起之秀。他们的本质都是通过 JavaScript 语法实现了shell script 命令的一些功能。比如利用jshint插件 实现 JavaScript 代码格式检查这一个功能。早期需要手动在命令行中输入 jshint test.js,而 Grunt 则通过文件 Gruntfile.js 进行配置

Gulp吸取了Grunt的优点,拥有更简便的写法,通过流(Stream)的概念来简化多任务之间的配置和输出,让任务更加简洁和容易上手。

Gulp 特点是代码驱动,写任务就和写普通的 Node.js 代码一样:

代码语言:javascript
复制
//?gulpfile.js
var?gulp?=?require('gulp');
var?jshint?=?require('gulp-jshint');
var?uglify?=?require('gulp-uglify');

//?代码检查任务?gulp?采取了pipe?方法,用流的方法直接往下传递
gulp.task('lint',?function()?{
??return?gulp.src('src/test.js')
????.pipe(jshint())
????.pipe(jshint.reporter('default'));
});

//?压缩代码任务
gulp.task('compress',?function()?{
??return?gulp.src('src/test.js')
????.pipe(uglify())
????.pipe(gulp.dest('build'));
});

//?将代码检查和压缩组合,新建一个任务
gulp.task('default',?['lint',?'compress']);

再一个对文件读取是流式操作(Stream),也就是说一次 I/O 可以处理多个任务,还是 less 的例子,Gulp 的流程就是:

读取 less 文件 -> 编译成 css -> 压缩处理 -> 存储到磁盘

在 Grunt 与 Gulp 对比看来还是比较推荐 Gulp!

webpack

https://webpack.js.org/

传统的模块化基于单种编程语言,目的是为了解耦和重用,而因为前端本身的特点(需要三种编程语言配合)以及能力限制,所以不能实现跨资源加载也就难以实现组件化。

而 Webpack 打破的这种思维局限,它的 Require anything 的理念在实现模块化的同时也能够很方便实现组件化,借助 Webpack 就可以很轻松的实现这种代码组织结构:

webpack打包流程示意图
webpack打包流程示意图

Webpack 的特点:

  • 把一切都视为模块:不管是 CSS、JS、Image 还是 HTML 都可以互相引用,通过定义 entry.js,对所有依赖的文件进行跟踪,将各个模块通过 loader 和 plugins 处理,然后打包在一起。
  • 按需加载:打包过程中 Webpack 通过 Code Splitting 功能将文件分为多个 chunks,还可以将重复的部分单独提取出来作为 commonChunk,从而实现按需加载。

Webpack 也是通过配置来实现管理,与 Grunt 不同的时,它包含的许多自动化的黑盒操作所以配置起来会简单很多(但遇到问题调试起来就很麻烦),一个典型的配置如下:

代码语言:javascript
复制
module.exports?=?{
????//插件项
????plugins:?[commonsPlugin],
????//页面入口文件配置
????entry:?{
????????index?:?'./src/js/page/index.js'
????},
????//入口文件输出配置
????output:?{
????????path:?'dist/js/page',
????????filename:?'[name].js'
????},
????module:?{
????????//加载器配置
????????loaders:?[
????????????{?test:?/\.css$/,?loader:?'style-loader!css-loader'?},
????????????{?test:?/\.js$/,?loader:?'jsx-loader?harmony'?},
????????????{?test:?/\.scss$/,?loader:?'style!css!sass?sourceMap'},
????????????{?test:?/\.(png|jpg)$/,?loader:?'url-loader?limit=8192'}
????????]
????},
????//其它解决方案配置
????resolve:?{
????????root:?'/Users/Bell/github/flux-example/src',?//绝对路径
????????extensions:?['',?'.js',?'.json',?'.scss'],
????????alias:?{
????????????AppStore?:?'js/stores/AppStores.js',
????????????ActionType?:?'js/actions/ActionType.js',
????????????AppAction?:?'js/actions/AppAction.js'
????????}
????}
};

参考文章:

SystemJS 探秘 https://zhuanlan.zhihu.com/p/402155045

System.js详解 https://www.cnblogs.com/tangxing/p/7223456.html

Javascript模块加载捆绑器Browserify Webpack和SystemJS用法 https://www.jdon.com/idea/js/javascript-module-loaders.html

browserify 中文文档与使用教程 https://zhuanlan.zhihu.com/p/76604976

curl.js: Incredible AMD Loader https://davidwalsh.name/curljs

用 Browserify 替换 require.js https://blog.csdn.net/nsrainbow/article/details/52736904

前端工程化——构建工具选型:grunt、gulp、webpack https://juejin.cn/post/6844903645700423693

差点被SystemJs惊掉了下巴,解密模块加载黑魔法 https://segmentfault.com/a/1190000039305322

https://www.digitalocean.com/community/tutorials/how-to-dynamically-import-javascript-with-import-maps

从systemjs的使用学习js模块化 https://segmentfault.com/a/1190000022278429

转载本站文章《前端模块化方案:前端模块化/插件化异步加载方案探索》, 请注明出处:https://www.zhoulujun.cn/html/webfront/engineer/Architecture/8753.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前端模块化序篇
  • ES6异步加载
  • es5时代模块加载器
    • AMD阵营
      • 超快速AMD入门 (Super Quick AMD Primer)
    • Require.JS
      • Browserify
        • Browserify缺点
      • SystemJS
        • SystemJS加载配置
    • es5时代模块打包方案
      • grunt
        • glup
          • webpack
          相关产品与服务
          腾讯云代码分析
          腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
          http://www.vxiaotou.com