对于目标1和2, 若是仅仅为了支持模块化的话,能够考虑使用AMD(RequireJS)或者CMD(SeaJS)这两种方案;但,咱们仍是但愿可以使用ES6的新特性,好比箭头函数,promise,async/await等颇有用的特性,因此,模块化的机制就采用了es6的import/export。javascript
对于目标3,Vue单文件那样的组件化,本质上是将一个组件对应的js/css/html放在一块儿,这样能够很方便的进行处理和复用;宏观上讲,每一个页面均可以经过一个个组件搭建起来,这种复用的方式,很简洁很强大。直接使用Vue的重构成本过高,因此,咱们但愿可以采用相似的思想实现,这个问题咱们后面会有探讨。css
使用了es6特性后,源代码就不可以直接在浏览器上运行了,须要通过构建工具的处理,好比fis3或者webpack;这样的话,每次都须要编译,开发的时候会不会不方便呢?这个问题在后面咱们会有方法来解决。html
现有工程用的构建工具就是fis1.9,不过仅用了混淆代码和压缩的功能;不过咱们发现fis3的功能已经足够实现咱们的需求了;因此,首先尝试的就是fis3.前端
fis3的构建过程能够归纳为三步:java
对文件属性的设置,以及插件的使用,都在fis-conf.js中配置就行;node
fis3其实天生就很是适合多页面工程的构建,由于fis3是不会修改你的项目结构,除非你fis3你打包的时候须要合并某些文件;用一句话来解释就是,fis3从全部文件上流过,根据文件对象上的设置对文件进行编译,全部文件编译完后,根据设置的打包配置进行打包。jquery
fis3的进一步的解释可参考1与2;webpack
.
├── assets
│ ├── images
│ │ ├── adv_course.png
│ │ ├── adv_realscreen.png
│ │ └── app_banner.jpg
│ └── scss
│ ├── a.scss
│ └── variables.scss
├── components // 组件
│ └── table
│ ├── table.es6
│ └── table.scss
├── libs //库文件
│ └── jquery-1.11.1.js
├── mock //用于开发的时候,设置代理
│ └── server.conf
├── modules //各个模块
│ ├── data.es6
│ ├── date.es6
│ └── text.es6
├── node_modules
├── pages
│ ├── pageA.es6 //模块化的js文件
│ ├── pageB.es6
│ ├── pageC.es6
│ └── pageD.js //非模块化的js文件
├── a.html
├── b.html
├── c.html
├── fis-conf.js
├── mod.js
├── package.json
└── README.md
复制代码
代码地址:fis3方案demo
git
配置文件fis-conf.jses6
fis.match('*.es6', {//对.es6后缀的文件,须要用babel将es6的代码转换为es5的
parser: fis.plugin('babel-6.x', {
plugins:['transform-runtime']
}),
rExt: '.js'
});
fis.match('*.scss', {
parser: fis.plugin('node-sass', {//将scss文件解析为css
// options...
}),
rExt: '.css'
})
fis.match('*.{js,es,es6,jsx,ts,tsx}', {//能够在js文件中require scss文件,有利于组件化
preprocessor: fis.plugin('js-require-css')
})
// 开启模块化开发
fis.match('/node_modules/**.js', {
isMod: true,
useSameNameRequire: true
});
fis.match('*.es6', {
isMod: true
});
fis.hook('commonjs', {
extList: ['.js', '.jsx', '.es6', '.es', '.ts', '.tsx']
});
fis.match('::package', {
postpackager: fis.plugin('loader')
});
fis.unhook('components');
fis.hook('node_modules');复制代码
三个目标的实现:
1. fis3是怎么支持模块化的?
fis.match('/node_modules/**.js', {
isMod: true,
useSameNameRequire: true
});
fis.match('*.es6', {
isMod: true
});
fis.hook('commonjs', {
extList: ['.js', '.jsx', '.es6', '.es', '.ts', '.tsx']
});
fis.unhook('components');
fis.hook('node_modules');复制代码
上面的配置之因此有这么多,是由于要对npm的node_modules模块的支持,由于babel模块是须要打包到线上的,而fis3不会自动对node_modules中的模块进行处理,因此就须要fis3-hook-node_modules,可参考3。
fis3编译的时候,会把es6模块文件用define函数进行包裹;同时把es6的import和export,变为commonjs的require和exports;简单来讲,define函数就是把模块和模块ID(默认是路径)进行映射,require就是经过模块ID(也就是路径)获得对应的模块。define的包裹效果以下:
define('modules/date.es6', function(require, exports, module) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var getDateStr = function getDateStr() {
return new Date().toLocaleDateString();
};
exports.getDateStr = getDateStr;
});复制代码
模块化的支持还须要mod.js这个文件(第一个加载执行),由于require和define这两个函数的定义就在mod.js中;
//a.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/assets/scss/a.scss">
<title>page a</title>
<script type="text/javascript" src="./mod.js"></script>
</head>
<body>
<p>
<img src="/assets/images/adv_course.png">
<button id="changePic">change pic</button>
</p>
<div class="bg-image"></div>
<span id="span-a-date"></span>
<span id="span-a-text"></span>
<p id="span-d-text"></p>
<button id="changeTextSync">change</button>
<button id="changeTextWithPromise">change with promise</button>
<button id="changeTextWithAsync">change with async</button>
<button id="changeTableText">change table text</button>
<div id="component-table"> </div>
<script type="text/javascript" src="./libs/jquery-1.11.1.js"></script>
<script type="text/javascript" src="./pages/pageD.js"></script>
<script type="text/javascript">
require('./pages/pageA')
</script>
</body>
</html>复制代码
fis3编译a.html,经过解析require('./pages/pageA'),将相关的依赖模块createScript插入a.html中,以下:
<script type="text/javascript" src="/mod.js"></script>
.....
<script type="text/javascript" src="/modules/date.js"></script>
<script type="text/javascript" src="/modules/text.js"></script>
<script type="text/javascript" src="/modules/data.js"></script>
<script type="text/javascript" src="/components/table/table.js"></script>
<script type="text/javascript" src="/pages/pageA.js"></script>
<script type="text/javascript" src="/libs/jquery-1.11.1.js"></script>
<script type="text/javascript" src="/pages/pageD.js"></script>
<script type="text/javascript">
require('pages/pageA.es6')
</script>
复制代码
当加载页面的时候,会顺序加载并执行各个模块js,因为模块已经用define函数包裹了,因此执行模块js文件的时候仅仅是把模块ID<-->模块函数factory;这种映射很快,不会卡顿后面的执行;
当最后执行require('./pages/pageA')的时候,全部依赖的模块都已经映射好了(至关于存在内存中了),能够直接require了。(至关于模拟出commonjs的同步加载的效果)
关于模块化的理解,能够见参考4。
2. 怎么支持es6?
es6的支持很简单,只须要这么配置就行
fis.match('*.es6', {//对.es6后缀的文件,须要用babel将es6的代码转换为es5的
parser: fis.plugin('babel-6.x', {
plugins:['transform-runtime']
}),
rExt: '.js'
});复制代码
遇到的问题是,不知道怎么把dependencies的模块一块儿打包,这个上面已经提到了。
demo中,尝试了箭头函数、Promise、async/await这三个特性,都可以很好的支持。
3. 怎么实现组件化呢?
在demo中,写了个简单table组件
// /components/table/table.es6,定义组件
require('./table.scss') // 组件的css直接在js中加载,很方便
export class MyTable {
constructor (el) {
this.el = el
}
setData (data) {
this.data = data
this.render()//数据更改的时候,从新渲染组件
}
render () {
let data = this.data, cnt = $('.component-table.'+this.el)
cnt.empty()
cnt.append('table:'+data)
}
}
// /pages/pageA.es6,使用组件
import {MyTable} from '/components/table/table.es6';
let table = new MyTable('pageA')
$('#changeTableText').click(()=>{
let num = parseInt(Math.random()*1000)
table.setData(num);
})
// pageA.html,组件的container
<div class="component-table pageA"> </div>复制代码
组件的layout在组件内部写好后,再render到对应的page的container中;固然组件的class应该得按照约定书写;这样的话,咱们就大概模拟了Vue的组件化思想。
fis3很是适合多页面的构建,由于fis3不会改变你的文件结构,因此不须要像webpack那样考虑不少的路径问题;使用fis3进行重构的方案很简单:须要使用模块化的文件后缀为.es6;新增components目录支持组件化;其余不须要改动,能够在现有工程上逐步升级,能够实现平滑的升级。
fis3有个缺点:没有成熟的社区,如今用的人彷佛少了,有问题得本身撸,固然这个还能够克服。
那么使用fis3构建后,有没有办法让咱们在开发的时候方便些呢?特别是api的代理,由于这样能够很方便前端调试。庆幸的是,fis3提供了server功能,咱们能够经过fis3 server start在本地启动一个web server,只要在/mock/server.conf中设置proxy代理,就能够将api代理到你想获取数据的服务器上,这种组合能够很让咱们很方便的调试,具体可参考5和6。
webpack算是如今最火热的构建工具,因此咱们也想要尝试下,看是否可以更方便的实现咱们的目标。
webpack的构建原理可大概分为五步:
有几个概念须要解释下:
用一句话来解释就是,webpack从配置的entry开始,根据loader和plugin编译entry及其依赖,再根据输出路径获得编译后的文件;webpack的进一步理解,可参考7与8。
目录的结构与fis3方案的差很少,只是把html和对应的js文件放在了一个目录下,方便webpack处理。
├── build
│ ├── utils.js
│ └── webpack.dev.conf.js //webpack配置文件
├── dist // build后的目录
│ ├── css
│ │ └── a.71d83539237d40d5d190.css
│ ├── images
│ │ ├── adv_course.d31756acb1d985ffb7a8a9ae8e989497.png
│ │ ├── adv_realscreen.708b822d3ff896f2fc1fc938676235c8.png
│ │ └── app_banner.c8eb7e13e6a2684bfa60d56416a07782.jpg
│ ├── js
│ │ ├── a-c102f7fbc2aace2a3b95.js
│ │ ├── b-2b3d38d6e9b425cdc5c9.js
│ │ └── c-dfc151f9ec73442ad091.js
│ ├── libs
│ │ ├── jquery-1.11.1.js
│ │ └── pageD.js
│ ├── a.html
│ ├── a_m.html
│ ├── b.html
│ └── c.html
├── node_modules
├── src
│ ├── assets
│ │ ├── images
│ │ │ ├── adv_course.png
│ │ │ ├── adv_realscreen.png
│ │ │ └── app_banner.jpg
│ │ └── scss
│ │ └── variables.scss
│ ├── components // 组件
│ │ └── table
│ │ ├── table.es6
│ │ └── table.scss
│ ├── libs //不须要build的放这里
│ │ ├── jquery-1.11.1.js
│ │ └── pageD.js
│ ├── modules // 模块
│ │ ├── data.es6
│ │ ├── date.es6
│ │ └── text.es6
│ └── pages // 页面
│ ├── a
│ │ ├── a.html
│ │ ├── a_m.html //与a.html公用一个pageA.es6
│ │ ├── a.scss
│ │ └── pageA.es6
│ ├── b
│ │ ├── b.html
│ │ └── pageB.es6
│ └── c
│ ├── c.html
│ └── pageC.es6
├── package.json
└── README.md复制代码
代码地址:webpack4方案demo
配置文件webpack.dev.conf.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const getEntries = ()=>{ // 多个入口依赖
return {
a: './src/pages/a/pageA.es6',
b: './src/pages/b/pageB.es6',
c: './src/pages/c/pageC.es6'
}
}
let webpackCfg = {
entry: getEntries(),
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/',
filename: 'js/[name]-[chunkhash].js'
},
devServer: {
index: 'a.html',
contentBase: false,
publicPath: '/',
port: 8080,
open: true,
proxy: {
'/stock/*': {
target: 'https://guorn.com',
changeOrigin: true
}
}
},
module: {
rules: [
{
test: /\.html$/,
use: {
loader: 'html-loader'
}
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader: "css-loader"
},
{
loader: "resolve-url-loader"
},
{
loader: "sass-loader",
options: {
sourceMap: true
}
}
]
})
},
{//es6的编译
test: /\.es6?$/,
loader: 'babel-loader'
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: 'url-loader',
// 配置 url-loader 的可选项
options: {
// 限制 图片大小 10000B,小于限制会将图片转换为 base64格式
limit: 10000,
// 超出限制,建立的文件格式
// build/images/[图片名].[hash].[图片格式]
name: 'images/[name].[hash].[ext]'//utils.getAssetsPath('images/[name].[hash].[ext]')
}
}
]
}
]
},
plugins: [
//静态资源输出,libs下的文件不用编译,完整输出到dist/libs中
new CopyWebpackPlugin([{
from: path.resolve(__dirname, "../src/libs"),
to: 'libs/',
ignore: ['.*']
}]),
new ExtractTextPlugin('css/[name].[hash].css')
]
}
//生成多个页面
var pages = [
{
filename: 'a.html',
template: 'src/pages/a/a.html',
chunks: 'a'
},
{
filename: 'a_m.html',
template: 'src/pages/a/a_m.html',
chunks: 'a'
},
{
filename: 'b.html',
template: 'src/pages/b/b.html',
chunks: 'b'
},
{
filename: 'c.html',
template: 'src/pages/c/c.html',
chunks: 'c'
}
]
pages.forEach(function(page) {
var conf = {
filename: page.filename, // 文件名,生成的html存放路径,相对于path
template: page.template, // html模板的路径
chunks: [page.chunks],
inject: 'body', // //js插入的位置
minify: { // 压缩HTML文件
removeComments: true, // 移除HTML中的注释
collapseWhitespace: false, // 删除空白符与换行符
removeAttributeQuotes: true
},
}
webpackCfg.plugins.push(new HtmlWebpackPlugin(conf))
});
module.exports = webpackCfg;复制代码
基本思路:
webpack处理的时候会遇到各类路径问题,可参考10;
三个目标的实现:
1. webpack是怎么支持模块化的?
webpack也是按照commonjs的规范来处理模块。
与fis3的模块化处理方案相比,二者本质上是同样的,比较大的不一样是:fis3是把全部依赖模块createScript插入html页面,而后当页面加载的时候调用define进行模块ID与模块的映射。
2. webpack是怎么支持es6的?
只要按照以下配置就能够了
webpack.dev.conf.js
{
test: /\.es6?$/,
loader: 'babel-loader'
},复制代码
.babelrc
{
"presets": [
["@babel/env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}]
// "@babel/stage-2"
],
"plugins": ["@babel/plugin-transform-runtime"]
}
复制代码
因为webpack是把依赖的模块直接打包到入口文件中,因此不须要像fis3那样单独考虑处理node_modules目录下的依赖模块。
关于babel的理解,可参考12.
3. 怎么实现组件化?
组件化的思路与fis3方案中的差很少,可参考fis3方案中的介绍。
无疑webpack很强大,社区也很成熟,不少问题都有解决方案,虽然更合适单页面工程的构建,但也适用多页面工程的构建。
webpack也有个webpack-dev-server提供了server功能,咱们能够经过在本地启动一个web server访问页面,同时也能够设置proxy代理,将api代理到你想获取数据的服务器上,开发仍是蛮方便的。
综合考虑,因为咱们须要兼容原有的项目,使用fis3构建的方案改动最小,三大目标也能够实现,同时也能够在兼容原有js的基础上一步步引入es6,因此咱们最后用的是fis3方案。