去年一篇《在 2016 年學 JavaScript 是一種什麼樣的體驗?》嚇壞了很多想要入行新同學和入行很久的老司機,感覺一下子前端世界已經看不懂了,做個頁面要那麼麻煩?當然如果你只是想要一個簡單的靜態頁面,這麼玩兒就是殺雞用牛刀了。但如果你準備開發一個 Web App,之後會不斷的迭代,有一個舒適的開發環境是及其重要的,那麼底怎麼樣的環境才會是舒適愉悅的呢?
比如這樣的一個環境:資源依賴可以安裝並模組化引用、可以使用很酷的 ES6 語法、可以使用 SASS 預處理器寫 CSS、程式碼可實時更新而不用一遍遍的手動重新整理頁面,這樣的開發環境你會不會覺得很爽!好,我們這就來配置一個這樣的環境!
基礎環境
首先,你需要一個 Node.js,然後 NPM 也會隨著 Node.js 一起裝上。
什麼是 NPM ?簡單的說 NPM 是用來下載安裝 Node.js 的第三方工具包的一個管理器。當然,現在也可以安裝瀏覽器中使用的包。提到包管理器,就不得不說下 Bower,Bower 之前一直是前端庫管理工具,一開始 NPM 只能釋出和安裝 Node.js 的包,所以 Bower 盛行一時,隨著 CommonJS 的普及,以及 UMD 規範的出現,讓 NPM 安裝前端瀏覽器 js 包成為了可能,隨著 NPM 生態的成熟,Bower 也就慢慢被人淡忘了~
Node.js 安裝完成後,可以執行以下命令驗證安裝是否成功:
$ node -v v6.11.0 $ npm -v 3.10.10
別急,Node.js 的部分還沒完,國內透過 NPM 的官方源安裝依賴好像很慢,動不動就要等上半天,如何解決?我們可以裝一個 nrm!nrm 是 npm registry 管理工具,可以自由切換 npm registry,然後命令列使用時依然是 npm ,國內有很多 npm 的映象,比如淘寶的 cnpm ,然而很多公司都架設了自己的私庫。什麼是私庫?私庫就是隻能在公司內網訪問,不能釋出到 npm 共享平臺的 npm 包,比如我們大公司私庫的 registry 的名稱就是 hnpm。不細說了,我們先裝一個試試:
$ npm install -g nrm
然後根據官方教程我們先切一個國內的 registry,比如大淘寶的:
$ nrm use cnpm
然後用 NPM 隨便安裝個什麼,看看速度如何?是不是很快^_^
等等,Node.js 還有。有的開發依賴包是有 Node.js 版本依賴的,我們知道 Node.js 不同大版本的功能還是差別很大的,但我們又不會一遍遍的解除安裝安裝吧?感覺好蠢!好吧,我們當然可以裝一個nvm,nvm?好像和 nrm 很像!nvm 是 Node.js 的版本管理工具,可以在多個終端切換和執行不同的 Node.js 版本,可以到這裡參考具體的安裝教程。不過 nvm 在 windows 下不能使用,沒關係,這裡還有幾個替代工具:nvm-window,gnvm 供你選擇。
同樣,我們執行下命令驗證安裝成果:
$ nvm --version 0.33.0
專案初始化
有了上面的工具我們就可以開始建立一個專案了,我們執行以下命令來開始一個專案:
mkdir my-app cd my-app npm init
執行 npm init 後你會看到你需要輸入專案的一些資訊,完成後回車確認,然後npm會在根目錄下建立一個叫 package.json 的檔案,你之後透過 --save 或者 --save-dev 安裝的依賴包都會出現在這個檔案裡。
先不管那麼多,我們在根目錄下建立一個 src 目錄,然後在 src 下建立index.js、index.html……,好吧,你可以按照下面的結構新建檔案:
. ├── package.json └── src ├── index.css ├── index.html └── index.js
在以下檔案中輸入程式碼:
index.js:
var el = document.createElement("div"), text = document.createTextNode("My App"); el.appendChild(text); document.body.appendChild(el);
index.html:
<!doctype html> <html> <head> <meta charset="utf-8" /> <title>My App</title> </head> <body> </body> </html>
我們要想辦法讓這個頁面跑起來,what??? 就這麼簡單?,把js引入 index.html 不就完事兒了嘛?當然沒那麼簡單,我們可是要搞高大上的東西的呢!
哈~跑題了,我們繼續。
首先我們要裝一個叫 webpack 的東西,它是一個模組打包器,也就是我們俗稱的構建工具,之前的那些 Grunt,Gulp 也都是構建工具,但是這年頭流行 webpack 了!開個玩笑,webpack 的可擴充套件性和可外掛化,以及把任何檔案都視為模組的概念得到了前端社群的一致推崇,而且在打包效率和按需分割檔案上都是其他幾個構建工具無法相比較的,當然 webpack 的配置太靈活,官方文件寫的太太太難看懂,也導致了很多初學者無從下手。
接下來我們就來配下這個神奇的工具吧。
自動構建
我們先安裝下 webpack:
npm install --save-dev webpack
然後在根目錄下新建一個 webpack.config.js 檔案,輸入以下程式碼:
let path = require("path"); module.exports = { entry: { app: path.resolve(__dirname, "src", "index.js") }, output: { filename: "[name].js", path: path.resolve(__dirname, "dist") } };
但要想在瀏覽器中訪問還得有個本地伺服器,好在 webpack 都幫我們想到了,我們可以裝一個webpack-dev-server:
npm install --save-dev webpack-dev-server
我們在 package.json 中增加個 npm scripts:
"scripts": { "start": "webpack-dev-server --port 3003" },
ok!我們執行下 npm start,在瀏覽器中訪問:http://localhost:3003。哎?好像哪裡不對!是的,你得告訴 webpack,你的 bundle(打包後的 js)要插入到哪個 html 模板,前面說過,webpack 是外掛化的,它把很多功能開放給了第三方來實現,他只是來負責拼裝的,好,現在我們需要安裝一個 html-webpack-plugin 外掛:
npm install --save-dev html-webpack-plugin
修改下 webpack-config.js:
let HtmlWebpackPlugin = require("html-webpack-plugin"), path = require("path"); module.exports = { entry: { ... }, ... plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, "src", "index.html") }) ] }
再次執行 npm start,頁面可以正常訪問了。
但是,這樣似乎有點 low,我們新增一個檔案 utils.js,搞點es6語法:
. ├── package.json └── src ├── index.css ├── index.html ├── index.js + └── utils + └── utils.js
utils.js:
export function wordsToSentence(...words) { return words.join(" "); }
修改 index.js
+ import { wordsToSentence } from "./utils/utils"; let el = document.createElement("div"), - text = document.createTextNode("My App"); + text = document.createTextNode( + wordsToSentence("Welcome", "to", "my", "app!") + ); el.appendChild(text); document.body.appendChild(el);
重新整理頁面後好像也沒什麼異常(你肯定用了 chrome 吧!),仔細看控制檯的 source 的 app.js(你的 bundle)的程式碼片段:
"use strict"; /* harmony export (immutable) */ __webpack_exports__["a"] = wordsToSentence; function wordsToSentence(...words) { return words.join(" "); }
值得注意的是,使用 ES6 時需要考慮那些沒有支援 ES6 的舊瀏覽器,雖然在 chrome 或者其他高階瀏覽器中沒有出現問題,但不能保證在其他瀏覽器中能正常執行。為了萬無一失,我們需要將 ES6 轉換為 ES5,也就是js程式碼轉換器,這類工具當今世界就屬 Babel 最牛逼了:
npm install --save-dev babel-loader babel-core
稍等,裝了 Babel 還沒法用,還得搞個 presets:
npm install --save-dev babel-preset-env
在根目錄下新建個 .babelrc,輸入配置:
{ "presets": ["env"] }
修改 webpack.config.js,增加 babel 的支援:
... module.exports = { ... module: { rules: [ { test: /\.js$/, loader: "babel-loader", include: path.resolve(__dirname, "src") } ] }, ... };
執行 npm start,找到控制檯 source 下的 app.js 程式碼片段:
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.wordsToSentence = wordsToSentence; function wordsToSentence() { for (var _len = arguments.length, words = Array(_len), _key = 0; _key < _len; _key++) { words[_key] = arguments[_key]; } return words.join(" "); }
已經成功轉換成 ES5 程式碼。但是,目前 ES6 Modules 是由 Babel 來轉的,你可以對比前後 2 次的程式碼片段的模組輸出部分。現在,webpack 2 已經內 4 置了 ES6 Modules 的轉換,據說效率和效能比 Babel 高!^_^沒驗證過哦,我們先試試,把 Babel 的模組轉換關了先:
.babelrc
{ "presets": [ ["env", { "modules": false }] ] }
執行 npm start 再次檢視輸出後的 app.js 的程式碼片段:
-Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.wordsToSentence = wordsToSentence; +/* harmony export (immutable) */ __webpack_exports__["a"] = wordsToSentence; function wordsToSentence() { ... }
模組輸出方式又回到了使用 Babel 前的程式碼。
js 的環境似乎已經準備就緒,但 css 還沒上場,我們來修改下 index.css:
#app { color: #57af09; }
同時將 css 匯入 bundle 入口,並修改下 index.js:
import "./index.css"; import { wordsToSentence } from "./utils/utils"; let el = document.createElement("div"), ... el.id = "app"; ...
有了樣式還不行,webpack 還需要相應的 loader 來處理 css 的模組:
npm i --save-dev style-loader css-loader
修改下 webpack.config.js:
... module.exports = { ... module: { rules: [ ... { test: /\.css$/, loader: ["style-loader", "css-loader"], include: path.resolve(__dirname, "src") } ] }, ... };
執行 npm start,現在可以看到頁面已經有了樣式。但是,我們說過,我們希望使用先進的武器:SASS。我們修改下 index.css:
$app-color: #57af09; #app { color: $app-color; }
再修改下檔案字尾:
. ├── package.json └── src - ├── index.css + ├── index.scss ...
修改 index.js 的入口:
-import "./index.css"; +import "./index.scss";
由於檔案(模組)型別變了,我們還需要一個 SASS 的 webpack loader:
npm install --save-dev sass-loader node-sass
再次修改 webpack.config.js:
... module.exports = { ... module: { rules: [ ... { - test: /\.css$/, + test: /\.scss$/, - loader: ["style-loader", "css-loader"], + loader: ["style-loader", "css-loader", "sass-loader"], include: path.resolve(__dirname, "src") } ] }, ... };
執行 npm start,webpack 編譯沒有報錯,頁面顯示一切正常!
程式碼自動更新(熱更新)
如果你嘗試修改 index.scss 的樣式,你有沒注意到一個問題:頁面會自動重新整理。但有時候我們在開發一個模組,比如 dialog,重新整理會導致你需要反覆的在頁面上操作才能看到這個 dialog 的樣式更新。那我們有沒有辦法不重新整理頁面又能看到程式碼的更新呢?
其實很簡單,因為 webpack-dev-server 已經內建了這樣的功能,我們只要配置下 package.json的 npm scripts:
"scripts": { "start": "webpack-dev-server --hot --inline --port 3003" },
注意到上面的程式碼,我們增加了 --hot --inline,讓開發環境有了熱更新的能力。我們重新執行 npm start,然後將你的瀏覽器和編輯器並排放置,然後反覆修改 index.scss,你會看到頁面不會重新整理,但樣式在自動的推送更新,這就是傳說中的熱更新。
結束語
到這裡,簡單(簡陋)的、現代化的前端開發環境已經有了基本的雛形,但是,本篇文章不是webpack 的使用指南,也不是 ES6 的語法教程,儘管如此,還是希望你透過本篇文章感受到前端開發在工程化領域的發展帶來的驚喜。
去年一篇《在 2016 年學 JavaScript 是一種什麼樣的體驗?》嚇壞了很多想要入行新同學和入行很久的老司機,感覺一下子前端世界已經看不懂了,做個頁面要那麼麻煩?當然如果你只是想要一個簡單的靜態頁面,這麼玩兒就是殺雞用牛刀了。但如果你準備開發一個 Web App,之後會不斷的迭代,有一個舒適的開發環境是及其重要的,那麼底怎麼樣的環境才會是舒適愉悅的呢?
比如這樣的一個環境:資源依賴可以安裝並模組化引用、可以使用很酷的 ES6 語法、可以使用 SASS 預處理器寫 CSS、程式碼可實時更新而不用一遍遍的手動重新整理頁面,這樣的開發環境你會不會覺得很爽!好,我們這就來配置一個這樣的環境!
基礎環境
首先,你需要一個 Node.js,然後 NPM 也會隨著 Node.js 一起裝上。
什麼是 NPM ?簡單的說 NPM 是用來下載安裝 Node.js 的第三方工具包的一個管理器。當然,現在也可以安裝瀏覽器中使用的包。提到包管理器,就不得不說下 Bower,Bower 之前一直是前端庫管理工具,一開始 NPM 只能釋出和安裝 Node.js 的包,所以 Bower 盛行一時,隨著 CommonJS 的普及,以及 UMD 規範的出現,讓 NPM 安裝前端瀏覽器 js 包成為了可能,隨著 NPM 生態的成熟,Bower 也就慢慢被人淡忘了~
Node.js 安裝完成後,可以執行以下命令驗證安裝是否成功:
$ node -v v6.11.0 $ npm -v 3.10.10
別急,Node.js 的部分還沒完,國內透過 NPM 的官方源安裝依賴好像很慢,動不動就要等上半天,如何解決?我們可以裝一個 nrm!nrm 是 npm registry 管理工具,可以自由切換 npm registry,然後命令列使用時依然是 npm ,國內有很多 npm 的映象,比如淘寶的 cnpm ,然而很多公司都架設了自己的私庫。什麼是私庫?私庫就是隻能在公司內網訪問,不能釋出到 npm 共享平臺的 npm 包,比如我們大公司私庫的 registry 的名稱就是 hnpm。不細說了,我們先裝一個試試:
$ npm install -g nrm
然後根據官方教程我們先切一個國內的 registry,比如大淘寶的:
$ nrm use cnpm
然後用 NPM 隨便安裝個什麼,看看速度如何?是不是很快^_^
等等,Node.js 還有。有的開發依賴包是有 Node.js 版本依賴的,我們知道 Node.js 不同大版本的功能還是差別很大的,但我們又不會一遍遍的解除安裝安裝吧?感覺好蠢!好吧,我們當然可以裝一個nvm,nvm?好像和 nrm 很像!nvm 是 Node.js 的版本管理工具,可以在多個終端切換和執行不同的 Node.js 版本,可以到這裡參考具體的安裝教程。不過 nvm 在 windows 下不能使用,沒關係,這裡還有幾個替代工具:nvm-window,gnvm 供你選擇。
同樣,我們執行下命令驗證安裝成果:
$ nvm --version 0.33.0
專案初始化
有了上面的工具我們就可以開始建立一個專案了,我們執行以下命令來開始一個專案:
mkdir my-app cd my-app npm init
執行 npm init 後你會看到你需要輸入專案的一些資訊,完成後回車確認,然後npm會在根目錄下建立一個叫 package.json 的檔案,你之後透過 --save 或者 --save-dev 安裝的依賴包都會出現在這個檔案裡。
先不管那麼多,我們在根目錄下建立一個 src 目錄,然後在 src 下建立index.js、index.html……,好吧,你可以按照下面的結構新建檔案:
. ├── package.json └── src ├── index.css ├── index.html └── index.js
在以下檔案中輸入程式碼:
index.js:
var el = document.createElement("div"), text = document.createTextNode("My App"); el.appendChild(text); document.body.appendChild(el);
index.html:
<!doctype html> <html> <head> <meta charset="utf-8" /> <title>My App</title> </head> <body> </body> </html>
我們要想辦法讓這個頁面跑起來,what??? 就這麼簡單?,把js引入 index.html 不就完事兒了嘛?當然沒那麼簡單,我們可是要搞高大上的東西的呢!
哈~跑題了,我們繼續。
首先我們要裝一個叫 webpack 的東西,它是一個模組打包器,也就是我們俗稱的構建工具,之前的那些 Grunt,Gulp 也都是構建工具,但是這年頭流行 webpack 了!開個玩笑,webpack 的可擴充套件性和可外掛化,以及把任何檔案都視為模組的概念得到了前端社群的一致推崇,而且在打包效率和按需分割檔案上都是其他幾個構建工具無法相比較的,當然 webpack 的配置太靈活,官方文件寫的太太太難看懂,也導致了很多初學者無從下手。
接下來我們就來配下這個神奇的工具吧。
自動構建
我們先安裝下 webpack:
npm install --save-dev webpack
然後在根目錄下新建一個 webpack.config.js 檔案,輸入以下程式碼:
let path = require("path"); module.exports = { entry: { app: path.resolve(__dirname, "src", "index.js") }, output: { filename: "[name].js", path: path.resolve(__dirname, "dist") } };
但要想在瀏覽器中訪問還得有個本地伺服器,好在 webpack 都幫我們想到了,我們可以裝一個webpack-dev-server:
npm install --save-dev webpack-dev-server
我們在 package.json 中增加個 npm scripts:
"scripts": { "start": "webpack-dev-server --port 3003" },
ok!我們執行下 npm start,在瀏覽器中訪問:http://localhost:3003。哎?好像哪裡不對!是的,你得告訴 webpack,你的 bundle(打包後的 js)要插入到哪個 html 模板,前面說過,webpack 是外掛化的,它把很多功能開放給了第三方來實現,他只是來負責拼裝的,好,現在我們需要安裝一個 html-webpack-plugin 外掛:
npm install --save-dev html-webpack-plugin
修改下 webpack-config.js:
let HtmlWebpackPlugin = require("html-webpack-plugin"), path = require("path"); module.exports = { entry: { ... }, ... plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, "src", "index.html") }) ] }
再次執行 npm start,頁面可以正常訪問了。
但是,這樣似乎有點 low,我們新增一個檔案 utils.js,搞點es6語法:
. ├── package.json └── src ├── index.css ├── index.html ├── index.js + └── utils + └── utils.js
utils.js:
export function wordsToSentence(...words) { return words.join(" "); }
修改 index.js
+ import { wordsToSentence } from "./utils/utils"; let el = document.createElement("div"), - text = document.createTextNode("My App"); + text = document.createTextNode( + wordsToSentence("Welcome", "to", "my", "app!") + ); el.appendChild(text); document.body.appendChild(el);
重新整理頁面後好像也沒什麼異常(你肯定用了 chrome 吧!),仔細看控制檯的 source 的 app.js(你的 bundle)的程式碼片段:
"use strict"; /* harmony export (immutable) */ __webpack_exports__["a"] = wordsToSentence; function wordsToSentence(...words) { return words.join(" "); }
值得注意的是,使用 ES6 時需要考慮那些沒有支援 ES6 的舊瀏覽器,雖然在 chrome 或者其他高階瀏覽器中沒有出現問題,但不能保證在其他瀏覽器中能正常執行。為了萬無一失,我們需要將 ES6 轉換為 ES5,也就是js程式碼轉換器,這類工具當今世界就屬 Babel 最牛逼了:
npm install --save-dev babel-loader babel-core
稍等,裝了 Babel 還沒法用,還得搞個 presets:
npm install --save-dev babel-preset-env
在根目錄下新建個 .babelrc,輸入配置:
{ "presets": ["env"] }
修改 webpack.config.js,增加 babel 的支援:
... module.exports = { ... module: { rules: [ { test: /\.js$/, loader: "babel-loader", include: path.resolve(__dirname, "src") } ] }, ... };
執行 npm start,找到控制檯 source 下的 app.js 程式碼片段:
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.wordsToSentence = wordsToSentence; function wordsToSentence() { for (var _len = arguments.length, words = Array(_len), _key = 0; _key < _len; _key++) { words[_key] = arguments[_key]; } return words.join(" "); }
已經成功轉換成 ES5 程式碼。但是,目前 ES6 Modules 是由 Babel 來轉的,你可以對比前後 2 次的程式碼片段的模組輸出部分。現在,webpack 2 已經內 4 置了 ES6 Modules 的轉換,據說效率和效能比 Babel 高!^_^沒驗證過哦,我們先試試,把 Babel 的模組轉換關了先:
.babelrc
{ "presets": [ ["env", { "modules": false }] ] }
執行 npm start 再次檢視輸出後的 app.js 的程式碼片段:
-Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.wordsToSentence = wordsToSentence; +/* harmony export (immutable) */ __webpack_exports__["a"] = wordsToSentence; function wordsToSentence() { ... }
模組輸出方式又回到了使用 Babel 前的程式碼。
js 的環境似乎已經準備就緒,但 css 還沒上場,我們來修改下 index.css:
#app { color: #57af09; }
同時將 css 匯入 bundle 入口,並修改下 index.js:
import "./index.css"; import { wordsToSentence } from "./utils/utils"; let el = document.createElement("div"), ... el.id = "app"; ...
有了樣式還不行,webpack 還需要相應的 loader 來處理 css 的模組:
npm i --save-dev style-loader css-loader
修改下 webpack.config.js:
... module.exports = { ... module: { rules: [ ... { test: /\.css$/, loader: ["style-loader", "css-loader"], include: path.resolve(__dirname, "src") } ] }, ... };
執行 npm start,現在可以看到頁面已經有了樣式。但是,我們說過,我們希望使用先進的武器:SASS。我們修改下 index.css:
$app-color: #57af09; #app { color: $app-color; }
再修改下檔案字尾:
. ├── package.json └── src - ├── index.css + ├── index.scss ...
修改 index.js 的入口:
-import "./index.css"; +import "./index.scss";
由於檔案(模組)型別變了,我們還需要一個 SASS 的 webpack loader:
npm install --save-dev sass-loader node-sass
再次修改 webpack.config.js:
... module.exports = { ... module: { rules: [ ... { - test: /\.css$/, + test: /\.scss$/, - loader: ["style-loader", "css-loader"], + loader: ["style-loader", "css-loader", "sass-loader"], include: path.resolve(__dirname, "src") } ] }, ... };
執行 npm start,webpack 編譯沒有報錯,頁面顯示一切正常!
程式碼自動更新(熱更新)
如果你嘗試修改 index.scss 的樣式,你有沒注意到一個問題:頁面會自動重新整理。但有時候我們在開發一個模組,比如 dialog,重新整理會導致你需要反覆的在頁面上操作才能看到這個 dialog 的樣式更新。那我們有沒有辦法不重新整理頁面又能看到程式碼的更新呢?
其實很簡單,因為 webpack-dev-server 已經內建了這樣的功能,我們只要配置下 package.json的 npm scripts:
"scripts": { "start": "webpack-dev-server --hot --inline --port 3003" },
注意到上面的程式碼,我們增加了 --hot --inline,讓開發環境有了熱更新的能力。我們重新執行 npm start,然後將你的瀏覽器和編輯器並排放置,然後反覆修改 index.scss,你會看到頁面不會重新整理,但樣式在自動的推送更新,這就是傳說中的熱更新。
結束語
到這裡,簡單(簡陋)的、現代化的前端開發環境已經有了基本的雛形,但是,本篇文章不是webpack 的使用指南,也不是 ES6 的語法教程,儘管如此,還是希望你透過本篇文章感受到前端開發在工程化領域的發展帶來的驚喜。