blog icon indicating copy to clipboard operation
blog copied to clipboard

代码模块化:<script>、RequireJS、Browserify 与 webpack

Open lmk123 opened this issue 9 years ago • 11 comments

作为公司里唯一的前端,代码组织方式一直是我在考虑的事情,而文章的标题就是我一步步实践过来的路程。

最开始的时候,我毫无例外的使用最普遍的 <script> 方式加载代码,我做的网页看起来就像这样:

<link rel="stylesheet" href="bootstrap.css">
<link rel="stylesheet" href="common1.css">
<link rel="stylesheet" href="common2.css">
<link rel="stylesheet" href="page.css">

<script src="jquery.js"></script>
<script src="common1.js"></script>
<script src="common2.js"></script>
<script src="page.js"></script>

就像大多数人做的那样,整个网站都用到的 style 与 script 放在最上面,某些页面之间公用的文件次之,最后是单个页面用到的文件。

后来我发现,这样做会有一些问题:

  • 文件的依赖关系非常混乱,为了用到一个 js,你可能需要预先加载另外一些 js ——另外一些 js 又依赖其它 js,用不了多久 <script> 标签就失控了。
  • 全局变量太多(jQuery、$、angular 等等)
  • 如果要开发网站用得到的公用文件(最初形态的“模块”),为了避免污染全局变量,所以要纳入到同一个全局变量(或者成为“命名空间”)里如 window.MyApp.module1,但时间长了就不知道这个命名空间里有些什么模块、这里面的模块相互之间是怎么依赖的了
  • 某些功能可能用的不多但文件很大,<script> 标签的加载方式无法做到按需加载;deferasync 只是延迟加载了,并没有做到按需加载。

为了彻底杜绝不可控的 <script> 标签,我在后来开始使用 RequireJS 并从此爱上了它(当然,我也专门为 RequireJS 写过一篇博文):

  • 每个 html 页面只需要一个 <script> 标签就够了
  • 附带的优化工具让创建相互独立的模块(由 html、css 与 js 组成)成为可能(为此我还写过一个工具专门用于把 html、css 和 js 合并为一个单独的 js 文件)
  • 杜绝全局变量,模块与模块之间的依赖关系只需要看看 require() 函数的使用就可以了
  • 简单易用的按需加载。下面的代码演示了如何在用户点击某个按钮时才加载用得到的模块:
button.onclick = () => require(['beautiful-alert'], myAlert => myAlert('Hello World.'));

基于上面的这些理由,我一直以来都坚决“拥护” AMD 的加载方式,即使是用 AngularJS 做的单页网站,我还是会使用它,并为此研究了如何异步加载 AngularJS 的控制器、指令、模块等(这是因为开发时会将代码分成很多块,而我需要一种方式在生产环境中将它们合理的合并在一起,最大化的提高网页加载速度。顺带一提,jQuery 也是用它来组织代码的,你可以在源码里找到熟悉的 define())。

即使后来 Browserify 的横空出世也没有改变我的立场。我发现 Browserify 在开发浏览器/ NodeJS 端的通用模块时很有用,并用它开发了 translation.js,但如果让我选择,我依然会在以后的项目中使用 RequrieJS 而不是 Browserify ——我甚至在开发划词翻译 v5.x 时也用了 RequireJS,而实际上由于 manifest.json 里的内容脚本不允许异步加载扩展里的脚本,所以 Chrome 扩展更适合用命名空间的方式组织代码。

最近开发划词翻译 v6.x 时,我考虑再三,还是决定使用命名空间而不是 RequireJS 来组织代码。一来扩展里文件也不多,用 RequireJS 有种大材小用,二来文件合并之后其实 RequireJS 作为加载器就不需要存在了,感觉有点占用空间(我不是处女座)。

但是在开发的时候,有一点让我很不爽——我的项目里除了 package.json ,还要加一个 bower.json。我在 #22 说明了为什么我不想再用 bower.json 了,而这时我发现,Vue.js (我的新宠)使用 webpack 来组织代码,还写了 vue-loader 用于加载 vue 组件。

虽说没有哪个工具能替代另一个工具,只有最适合的工具——但 Webpack 完全能替代 Browserify,并为开发模块、单页网站或是多页网站提供了更强大的功能。实际上我很早就 Star 了它,但在简单的看了文档之后觉得学习成本有点高,所以就先放着了。

然而今天,我花了一个下午的时间提交了一个 PR,并最终成功用它取代了命名空间的代码组织方式。我发现如果我不去看它生成的那一坨代码,在浏览器端使用 CMD 来组织代码的方式我还是能接收的。

并且,我顺利的删除了跟 Bower 有关的所有文件,项目里只有一个 package.json 的感觉舒服多了。

不过我想说的是,webpack 虽然能很好的支持多页网站,但这会让配置变得很复杂(主要的复杂度体现在将多个文件的通用模块分割开来),并且为了让输出的文件在不同的文件夹下,你需要使用多个 webpack.config.js(我暂时没有找到其他将文件输出在不同文件夹的方法)。但在如今这个 AngularJS 与 Vue.js 越来越流行的时代,webpack 在单页网站及模块开发上还是很有用的。

lmk123 avatar Nov 18 '15 11:11 lmk123

请问一下啊,划词翻译5.x-master里的selection.dot定义的html标记怎么理解?为什么chrome可以解析?如何使用这种自定义标记?

luxuelin avatar Jan 06 '16 05:01 luxuelin

@luxuelin selection.dot 是 doT 模板,html 标记是为了不与网页的样式产生冲突,我自己定义的 html 标记

lmk123 avatar Jan 06 '16 06:01 lmk123

@luxuelin 因为划词翻译会嵌入到各种各样的网页里,如果某一个网页定义了一条样式:

div { background-color: red; }

那同样会影响到划词翻译的样式。 为了避免这样的样式冲突,同时也为了让我的 html 看起来更加语义化,我使用了自定义标记(例如把所有 div 替换成了 lmk-div),这样网页上的样式就不会影响到划词翻译的样式了。

当浏览器遇到这样的自定义标记时,你可以简单的理解为浏览器会把它当做 span 一样看待。

lmk123 avatar Jan 06 '16 06:01 lmk123

@lmk123 谢谢,明白了,看了一下http://www.w3.org/html/ig/zh/wiki/HTML5/elements#htmlelement ,如果出现了不在H5标准中的标记,应该是默认当成HTMLUnknownElement,而HTMLUnknownElement继承自HTMLElement,文档中列出了属性和方法。

luxuelin avatar Jan 06 '16 06:01 luxuelin

@luxuelin You got it :)

lmk123 avatar Jan 06 '16 06:01 lmk123

我用划词翻译有很长时间了,昨天在某qq群讨论翻译一本Erlang语言的书时,想到以前做过的一个类似划词翻译的功能的插件,于是自己实现了一版,当我扒开您的插件源码时,发现了开源地址,我现在想在您的插件基础上稍加改造,在您的划词翻译div中增加一个表单,再增加一个服务端,用于大家人肉翻译文档使用,我现在是在5.x基础上增加功能,做好以后给您试用一下:)

luxuelin avatar Jan 06 '16 06:01 luxuelin

机器翻译是不准确的,我的设计是想增加一个人工校准,当打开了某个页面时,针对每一个完整的句子,检索数据库中是否有人工翻译结果,如果有结果,人工翻译内容插入到原文句子后面(或替换掉)。

luxuelin avatar Jan 06 '16 06:01 luxuelin

这个想法看起来很不错,加油 :)

对划词翻译源码的使用只要遵循 GPL v3 协议就可以了。

lmk123 avatar Jan 06 '16 06:01 lmk123

写好以后源码会发布到GitHub,或者作为一个划词翻译的版本分支,肯定是继承划词翻译开源协议:)

luxuelin avatar Jan 06 '16 06:01 luxuelin

请教一下,requireJS,browserify和webpack在单页应用中都能做到按需加载/懒加载吗? 我也是粗略看了下文档,还没决定学习使用哪个,发现browserify似乎是把所有以按CommonJS规范写的js在node运行一句代码,编译打包成一整个js文件,这样不是不能懒加载了吗) thanks~

imdoge avatar Jan 13 '16 07:01 imdoge

@imdoge RequirJS 与 Webpack 都是支持按需加载的,Browserify 我没有深入研究过,但我猜它应该是不支持的。

我推荐你学习 Webpack。

lmk123 avatar Jan 13 '16 07:01 lmk123