最近将重构系统,需要重新搭建前端项目,遂将前端工程化相关知识进行整理。
模块化规范
传统开发模式的弊端
还记得早期没使用模块化的项目,一旦项目复杂起来,依赖变多,就会出现各种各样莫名奇妙的问题。
- 命名冲突,这会导致本来运行良好的代码,突然有一天不行了,那你就要找找是不是别人把你的方法覆盖了。
- 依赖问题,有依赖关系的js代码,必须按照正确的顺序通过
script
标签进行加载。
模块化规范
通过模块化的方式,可以把单独的功能封装到模块中,通过api向外暴露方法或者变量,同时也可以依赖别的模块。
增强了代码的可重用性,可维护性。
我们看看有哪些模块化规范
AMD,CMD以及CommonJs都并非官方规范,只有ES6中模块化规范才是语言层面的通用化标准。
AMD
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
引入requireJs
//通过async保证 requireJs的异步加载,defer保证IE兼容
<script src="js/require.js" defer async="true" ></script>
通过define
函数定义模块
//可以指定依赖,加载当前模块之前先加载 mylib
define(['myLib'], function(myLib){
function foo(){
myLib.doSomething();
}
return {
foo : foo
};
});
通过require
加载模块
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
});
CMD
CMD 是 sea.js 在推广过程中对模块定义的规范化产出
加载入口模块
// 加载入口模块
seajs.use("...)
定义模块
// 所有模块都通过 define 来定义
define(function(require, exports, module) {
// 通过 require 引入依赖
var $ = require('jquery');
var Spinning = require('./spinning');
// 通过 exports 对外提供接口
exports.doSomething = ...
// 或者通过 module.exports 提供整个接口
module.exports = ...
});
AMD和CMD的区别
CMD 推崇依赖就近,AMD 推崇依赖前置
AMD优点:加载快速,尤其遇到多个大文件,因为并行解析,所以同一时间可以解析多个文件。
AMD缺点:并行加载,异步处理,加载顺序不一定,可能会造成一些困扰,甚至为程序埋下大坑。
CMD优点:因为只有在使用的时候才会解析执行js文件,因此,每个JS文件的执行顺序在代码中是有体现的,是可控的。
CMD缺点:执行等待时间会叠加。因为每个文件执行时是同步执行(串行执行),因此时间是所有文件解析执行时间之和,尤其在文件较多较大时,这种缺点尤为明显。
CommonJs
服务端的模块化规范
模块导出
module.exports.add = function (x, y) {
return x + y
}
模块导入
var myModule = require('./test')
myModule.add(1,2)
ES6模块化规范
ES6提出的模块化规范是语言层面的,也就是说在浏览器端和服务端都可以使用(理想情况下)
- 每个js文件都是单独的模块
- 导出模块用
export
- 导入模块用
import
babel编译器
既然ES6都给出了模块化规范,为什么还需要babel编译器呢?
语言标准是标准,并不意味着了浏览器和node.js已经实现标准,即使从某个版本开始已经支持,但客户不一定更新客户端。通过babel我们可以尽情的使用 ES6的新语法,而不用考虑兼容性问题,babel会把新语法转换成老的js语法。
babel、babel-polyfill 和 babel-runtime
- babel 将 Javascript 分为 synax(语法)和 api 两部分,babel 只负责编译 ECMAScript 的最新语法,比如 let、 const、 箭头函数等,而对于新的 api 比如 Promise、include、Object.assign 这种则不进行编译,而是交给 babel-polyfill 进行编译。
- babel-polyfill 负责将新的 API 封装成浏览器可识别的 API 来覆盖原生,并将所有生成的覆盖代码插入项目源码。
- babel-polyfill 虽然提供了新的 API 支持,但是缺陷也比较明显:
全局注册覆盖的 API,污染全局变量
会将新的 API 全部封装导出到源码,不管项目中有没有使用过相关内容,导致导出项目包增大。 - babel-runtime 针对 babel-polyfill 的问题做了优化:
通过导出包 / 引入包的方式提供新封装的 API ,解决了污染全局变量的问题
结合 babel-transform-runtime-plugin 实现自动检测项目所用到的 API ,有选择的编译和引入,解决了导出包增大的问题。
但是 babel-runtime 有个问题,就是不能支持实例上的新增 API 方法,例如[1,2,3].includes(1) 这种。 - babel-polyfill 和 babel-runtime 对比:
babel-polyfill 适合在比较大的项目中引入。
babel-runtime 适合在组件、类库中引用。
babel的编译原理
转译分为三个阶段:
- 解析,将代码解析生成抽象语法树AST,也就是词法分析与语法分析的过程
- 转换,对语法树进行变换方面的一系列操作。通过babel-traverse,进行遍历并作添加、更新、删除等操作
- 生成,通过babel-fenerator将变换后AST转换为JS代码。
babel插件的原理
babel插件介入的是上图的Transform过程,我们可以修改AST,从而影响最终AST
webpack打包
本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack
处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency
graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
传统开发模式弊端
- 文件依赖关系错综复杂
- 静态资源请求效率低
- 模块化支持不友好
- 浏览器对高级javascript特性兼容程度较低
webpack的好处
webpack提供了友好的模块化支持,以及代码压缩混淆,处理js兼容问题,性能优化等强大功能,从而让程序员的工作重心放到具体的功能实现上。
loader 和 plugin 的区别
- Loader直译为"加载器"。Webpack将⼀切⽂件视为模块,但是webpack原⽣是只能解析js⽂件,如果想将其他⽂件也打包的话,就会⽤到 loader 。 所以Loader的作⽤是让webpack拥有了加载和解析⾮JavaScript⽂件的能⼒。
- Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运⾏的⽣命
周期中会⼴播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
常用的loader
配置方式如下
module:{
rules :[
{
test:/\.css$/,
use:['style-loader','css-loader']
},
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
},
{
test:/\.jpg|png|gif$/,
use:['url-loader?limit=470?&outputPath=images']
},
{
test:/\.js$/,
use:'babel-loader',
exclude :'/node_modules/'
},
{
test:/\.vue$/,
use:['vue-loader'],
}
]
},
- file-loader:把⽂件输出到⼀个⽂件夹中,在代码中通过相对 URL 去引⽤输出的⽂件
- url-loader:和 file-loader 类似,但是能在⽂件很⼩的情况下以 base64 的⽅式把⽂件内容注⼊到代码中去
- source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试
- image-loader:加载并且压缩图⽚⽂件
- babel-loader:把 ES6 转换成 ES5
- css-loader:加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性
- style-loader:把 CSS 代码注⼊到 JavaScript 中,通过 DOM 操作去加载 CSS,通常配合css-loader一起使用
- eslint-loader:通过 ESLint 检查 JavaScrip
常用的plugin
配置方式如下:
plugins : [htmlplugin,cleanPlugin,vueLoaderPlugin],
- define-plugin:定义环境变量
- html-webpack-plugin:简化html⽂件创建
- uglifyjs-webpack-plugin:通过 UglifyES 压缩 ES6 代码
- webpack-parallel-uglify-plugin: 多核压缩,提⾼压缩速度
- webpack-bundle-analyzer: 可视化webpack输出⽂件的体积
- mini-css-extract-plugin: CSS提取到单独的⽂件中,⽀持按需加载
引入vue框架
现在我们已经可以通过webpack打包应用,那我们如何引入vue框架呢?直接创建vue文件,webpack是不认识的,所以需要引入vue-loader,来解析vue文件
//添加插件
plugins : [htmlplugin,cleanPlugin,vueLoaderPlugin],
//配置加载器
module.rules:[
{ test:/\.vue$/,
use:['vue-loader'],}
]
配置好之后我们就可以在项目里使用vue框架了