對於目標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方案。