這一次我們談?wù)勀K問題。

通常我們希望這個(gè)項(xiàng)目可以分為多個(gè)獨(dú)立的模塊,比如,上一次提高的 hello 函數(shù),如果我們定義為一個(gè)模塊,其它模塊引用之后,直接調(diào)用就好了。在前端怎么使用模塊呢?這可說來話長了。

如果我們把 hello 函數(shù)定義在文件 hello.js 中,內(nèi)容如下:

function hello(){
    alert("Hello, Webpack!");
}

然后把主入口函數(shù) index.js 的內(nèi)容寫成下面的內(nèi)容,你應(yīng)該會(huì)得到一個(gè)錯(cuò)誤信息。

require("./hello");

hello();

對話框是彈不出來的。錯(cuò)誤信息如下:

Uncaught ReferenceError: hello is not defined, 明明定義了 hello 函數(shù),卻偏偏說找不到。

1. CommonJs 模塊

CommonJs 是目前比較流行,也是出現(xiàn)較早的模塊技術(shù),它誕生于 NodeJs,使用起來其實(shí)比較簡單。

首先,它把一個(gè)獨(dú)立的文件看成一個(gè)模塊,比如上面的 hello.js 文件,就可以當(dāng)成一個(gè)模塊。模塊的名稱就是文件名稱,但是可以不用提供擴(kuò)展名 .js,直接使用文件名就可以。

在導(dǎo)入一個(gè)模塊的時(shí)候,使用 require 函數(shù),注意是函數(shù),并不是關(guān)鍵字,JavaScript 并沒有提供這個(gè)關(guān)鍵字。函數(shù)的參數(shù)就是模塊名稱,不過,要注意模塊分為兩種,自定義的模塊和系統(tǒng)模塊。

自定義的模塊必須使用 . 或者 .. 開頭的相對路徑,如果都在同一個(gè)目錄下,也需要使用 . 來表示當(dāng)前路徑,比如上面用到的 require("./hello")。

不是使用 . 或者 .. 開始的相對路徑的,都稱為系統(tǒng)模塊,系統(tǒng)模塊的路徑其實(shí)在 node_modules 文件夾中,每個(gè)子文件夾就是一個(gè)系統(tǒng)模塊。

require 函數(shù)的返回結(jié)果就是模塊導(dǎo)出的內(nèi)容。

我們的模塊沒有導(dǎo)出任何內(nèi)容。所以,雖然被 index 引用了,但是在 index 中卻是無法訪問的。這也說明模塊中定義的函數(shù)其實(shí)是局部函數(shù),并不是通常意義上的全局函數(shù)了。

我們要在模塊中導(dǎo)出內(nèi)容怎么辦呢,CommonJs 提供了 exports 對象。

在 CommonJs 模塊中,希望導(dǎo)出的內(nèi)容必須通過 exports 對象,這是 CommonJs 系統(tǒng)提供的系統(tǒng)對象,我們可以直接使用。導(dǎo)出的內(nèi)容以鍵值對的形式定義到這個(gè)對象上,鍵就是導(dǎo)出的名稱,值就是準(zhǔn)備導(dǎo)出的內(nèi)容。

比如,我們希望將 hello 函數(shù)導(dǎo)出為名為函數(shù)的函數(shù),好像挺繞的,其實(shí)很簡單。

function hello(){
    alert("Hello, Webpack!");
}

exports.hello = hello;

將 index.js 函數(shù)的定義修改為

var hello = require("./hello").hello; hello();

重新執(zhí)行 webpack 命令,刷新網(wǎng)頁,你會(huì)看到函數(shù)執(zhí)行了。

 

2. AMD 模塊

另外一個(gè)著名的模塊系統(tǒng)稱為 AMD 模塊,全稱為 Asynchromous Module Defination,翻譯過來就是異步模塊規(guī)范,它是由 RequireJs 推動(dòng)的。

首先,一個(gè)文件也被看作一個(gè)模塊,所以,我們還拿 hello.js 作為一個(gè)模塊吧。

其次,定義模塊使用 define 函數(shù),函數(shù)的第一個(gè)參數(shù)是當(dāng)前模塊依賴的模塊數(shù)組,如果沒有依賴的函數(shù),可以使用一個(gè)空的數(shù)組表示。

復(fù)制代碼
define([], function(){ return {
        hello: function(){
            alert("Hello, Webpack!");
        }
    };
});
復(fù)制代碼

 

第二個(gè)參數(shù)就是模塊的定義函數(shù),這個(gè)函數(shù)的返回內(nèi)容就是模塊的導(dǎo)出內(nèi)容。我們這里導(dǎo)出一個(gè)對象包含我們定義的函數(shù)的對象。

最后,我們修改一下 index.js 來導(dǎo)入定義好的 AMD 模塊,并調(diào)用 hello 函數(shù)。

define(['./hello'], function( helloModule ){
    helloModule.hello();
})

 

這次,我們的 index 依賴提前定義好的 hello 模塊,注意模塊命名的方式還是注意自定義模塊和系統(tǒng)模塊的區(qū)別,在第二個(gè)函數(shù)參數(shù)中,它的參數(shù)就是被依賴模塊導(dǎo)出的內(nèi)容,所以這里的 helloModule 就是一個(gè)對象,它的 hello 屬性就是我們定義好的函數(shù)。

3. ES 2015 和 TypeScript 中的模塊

在 ES2015 中,定義了關(guān)鍵字,注意是關(guān)鍵字,不是函數(shù)了,模塊導(dǎo)出的關(guān)鍵字是 export , 導(dǎo)入就是 import 了。

所以,hello 函數(shù)可以這樣定義了。

export default function() {
    alert("Hello, Webpack!");
}; 

 

而 index.js 就可以這樣定義。

import hello from "./hello";

hello();

 

但是,需要注意的是,你必須有 ES2015 的運(yùn)行環(huán)境才行,目前很多瀏覽器不支持怎么辦呢?

我們可以將 ES2015 的代碼先翻譯成標(biāo)準(zhǔn)的 JavaScript 代碼,就可以執(zhí)行了。

而 TypeScript 作為 ES2015 的超集,我們就使用 TypeScript 來實(shí)現(xiàn)。

首先,你必須確認(rèn)你已經(jīng)安裝了 TypeScript 的編譯器,全局安裝 TypeScript

npm -g install typescript

 

安裝之后,可以執(zhí)行 tsc -v 來檢查版本,目前最新是 2.0.3 版本。

將 hello.js 重新改名為 hello.ts,將 index.js 改名為 index.ts. ts 是 TypeScript 文件的擴(kuò)展名。

你可以手工執(zhí)行命令 tsc 分別將兩個(gè) typescript 文件編譯為 javascript 文件。

生成的 hello.js

復(fù)制代碼
"use strict"; function default_1() {
    alert("Hello, Webpack!");
}
exports.__esModule = true;
exports["default"] = default_1;
;
復(fù)制代碼

 

生成的 index.js

"use strict"; var hello_1 = require("./hello");
hello_1["default"]();

 

 

然后,再次執(zhí)行 webpack 重新打包,你會(huì)發(fā)現(xiàn)結(jié)果仍然正常執(zhí)行。

4. 使用 tsloader 

在第三步,我們可以手工使用 tsc 將 TypeScript 代碼編譯為 JavaScript 代碼之后進(jìn)行打包,但是這樣太麻煩了,使用 ts-loader 我們可以讓 webpack 自動(dòng)先調(diào)用 tsc 將 TypeScript 代碼編譯為 JavaScript 代碼,然后再自動(dòng)進(jìn)行打包工作。

首先,當(dāng)然要安裝 ts-loader 插件了。

npm install ts-loader --save-dev

 

目前是 0.9.5 版本。

為了在項(xiàng)目中使用 typescript,你還需要本地安裝 typescript 一次。

npm install typescript --save-dev

 

還需要配置一下 TypeScript 如何編譯代碼。在當(dāng)前目錄中創(chuàng)建名為 tsconfig.json 的配置文件。

復(fù)制代碼
{ "compilerOptions": { "target": "es5", "sourceMap": true }, "exclude": [ "node_modules" ]
}
復(fù)制代碼

 

這里的配置是說,希望生成 es5 標(biāo)準(zhǔn)的 JavaScript 目標(biāo)文件。

然后,配置 webpack 使用 ts-loader

復(fù)制代碼
var HtmlwebpackPlugin = require('html-webpack-plugin');

module.exports = { // 入口 entry: "./index.ts", // 輸出的文件名  output: {
        filename: 'bundle.js' },
    resolve: { // Add `.ts` and `.tsx` as a resolvable extension. extensions: ['', '.ts', '.js']
    }, // 添加我們的插件 會(huì)自動(dòng)生成一個(gè)html文件  plugins: [ new HtmlwebpackPlugin({
            title: 'Hello Webpack' })
    ],
    module: {
        loaders: [ // all files with a `.ts` extension will be handled by `ts-loader` { test: /\.ts$/, loader: 'ts-loader' }
        ]
    }
};
復(fù)制代碼

 

這里增加了兩個(gè)內(nèi)容,一個(gè)是 resolve 配置,這樣我們只需要使用模塊名稱,webpack 會(huì)自動(dòng)添加后綴來查找模塊文件。另一個(gè)就是 module 中的 loader 了,這里是說遇到了 .ts 擴(kuò)展名的文件,需要先使用 ts-loader 處理之后再打包。

現(xiàn)在,重新執(zhí)行 webpack 命令,你會(huì)發(fā)現(xiàn)一切都已經(jīng)處理好了,刷新頁面,你應(yīng)該看到代碼正確執(zhí)行了。

5. 總結(jié)

webpack 可以直接支持 CommonJs 和 AMD 模塊,對于 TypeScript ,我們還需要安裝 TypeScript 和 ts-loader.

需要注意的是 Angular 2 使用 TypeScript 作為主力語言,你能做到這里,離 Angular 2 已經(jīng)很近了。