在舒適的開發(fā)環(huán)境中做前端是一種怎樣的體驗?
去年一篇《在 2016 年學 JavaScript 是一種什么樣的體驗?》嚇壞了很多想要入行新同學和入行很久的老司機,感覺一下子前端世界已經(jīng)看不懂了,做個頁面要那么麻煩?當然如果你只是想要一個簡單的靜態(tài)頁面,這么玩兒就是殺雞用牛刀了。但如果你準備開發(fā)一個 Web App,之后會不斷的迭代,有一個舒適的開發(fā)環(huán)境是及其重要的,那么底怎么樣的環(huán)境才會是舒適愉悅的呢?
比如這樣的一個環(huán)境:資源依賴可以安裝并模塊化引用、可以使用很酷的 ES6 語法、可以使用 SASS 預處理器寫 CSS、代碼可實時更新而不用一遍遍的手動刷新頁面,這樣的開發(fā)環(huán)境你會不會覺得很爽!好,我們這就來配置一個這樣的環(huán)境!
基礎環(huán)境
首先,你需要一個 Node.js,然后 NPM 也會隨著 Node.js 一起裝上。
什么是 NPM ?簡單的說 NPM 是用來下載安裝 Node.js 的第三方工具包的一個管理器。當然,現(xiàn)在也可以安裝瀏覽器中使用的包。提到包管理器,就不得不說下 Bower,Bower 之前一直是前端庫管理工具,一開始 NPM 只能發(fā)布和安裝 Node.js 的包,所以 Bower 盛行一時,隨著 CommonJS 的普及,以及 UMD 規(guī)范的出現(xiàn),讓 NPM 安裝前端瀏覽器 js 包成為了可能,隨著 NPM 生態(tài)的成熟,Bower 也就慢慢被人淡忘了~
Node.js 安裝完成后,可以執(zhí)行以下命令驗證安裝是否成功:
$ node -v v6.11.0 $ npm -v 3.10.10
別急,Node.js 的部分還沒完,國內(nèi)通過 NPM 的官方源安裝依賴好像很慢,動不動就要等上半天,如何解決?我們可以裝一個 nrm!nrm 是 npm registry 管理工具,可以自由切換 npm registry,然后命令行使用時依然是 npm ,國內(nèi)有很多 npm 的鏡像,比如淘寶的 cnpm ,然而很多公司都架設了自己的私庫。什么是私庫?私庫就是只能在公司內(nèi)網(wǎng)訪問,不能發(fā)布到 npm 共享平臺的 npm 包,比如我們大公司私庫的 registry 的名稱就是 hnpm。不細說了,我們先裝一個試試:
$ npm install -g nrm
然后根據(jù)官方教程我們先切一個國內(nèi)的 registry,比如大淘寶的:
$ nrm use cnpm
然后用 NPM 隨便安裝個什么,看看速度如何?是不是很快^_^
等等,Node.js 還有。有的開發(fā)依賴包是有 Node.js 版本依賴的,我們知道 Node.js 不同大版本的功能還是差別很大的,但我們又不會一遍遍的卸載安裝吧?感覺好蠢!好吧,我們當然可以裝一個nvm,nvm?好像和 nrm 很像!nvm 是 Node.js 的版本管理工具,可以在多個終端切換和運行不同的 Node.js 版本,可以到這里參考具體的安裝教程。不過 nvm 在 windows 下不能使用,沒關(guān)系,這里還有幾個替代工具:nvm-window,gnvm 供你選擇。
同樣,我們執(zhí)行下命令驗證安裝成果:
$ nvm --version 0.33.0
項目初始化
有了上面的工具我們就可以開始創(chuàng)建一個項目了,我們執(zhí)行以下命令來開始一個項目:
mkdir my-app cd my-app npm init
執(zhí)行 npm init 后你會看到你需要輸入項目的一些信息,完成后回車確認,然后npm會在根目錄下創(chuàng)建一個叫 package.json 的文件,你之后通過 --save 或者 --save-dev 安裝的依賴包都會出現(xiàn)在這個文件里。
先不管那么多,我們在根目錄下創(chuàng)建一個 src 目錄,然后在 src 下創(chuàng)建index.js、index.html……,好吧,你可以按照下面的結(jié)構(gòu)新建文件:
. ├── 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 不就完事兒了嘛?當然沒那么簡單,我們可是要搞高大上的東西的呢!
哈~跑題了,我們繼續(xù)。
首先我們要裝一個叫 webpack 的東西,它是一個模塊打包器,也就是我們俗稱的構(gòu)建工具,之前的那些 Grunt,Gulp 也都是構(gòu)建工具,但是這年頭流行 webpack 了!開個玩笑,webpack 的可擴展性和可插件化,以及把任何文件都視為模塊的概念得到了前端社區(qū)的一致推崇,而且在打包效率和按需分割文件上都是其他幾個構(gòu)建工具無法相比較的,當然 webpack 的配置太靈活,官方文檔寫的太太太難看懂,也導致了很多初學者無從下手。
接下來我們就來配下這個神奇的工具吧。
自動構(gòu)建
我們先安裝下 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!我們執(zhí)行下 npm start,在瀏覽器中訪問:http://localhost:3003。哎?好像哪里不對!是的,你得告訴 webpack,你的 bundle(打包后的 js)要插入到哪個 html 模板,前面說過,webpack 是插件化的,它把很多功能開放給了第三方來實現(xiàn),他只是來負責拼裝的,好,現(xiàn)在我們需要安裝一個 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') }) ] }
再次執(zhí)行 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 或者其他高級瀏覽器中沒有出現(xiàn)問題,但不能保證在其他瀏覽器中能正常運行。為了萬無一失,我們需要將 ES6 轉(zhuǎn)換為 ES5,也就是js代碼轉(zhuǎn)換器,這類工具當今世界就屬 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') } ] }, ... };
執(zhí)行 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(' '); }
已經(jīng)成功轉(zhuǎn)換成 ES5 代碼。但是,目前 ES6 Modules 是由 Babel 來轉(zhuǎn)的,你可以對比前后 2 次的代碼片段的模塊輸出部分。現(xiàn)在,webpack 2 已經(jīng)內(nèi) 4 置了 ES6 Modules 的轉(zhuǎn)換,據(jù)說效率和性能比 Babel 高!^_^沒驗證過哦,我們先試試,把 Babel 的模塊轉(zhuǎn)換關(guān)了先:
.babelrc
{ "presets": [ ["env", { "modules": false }] ] }
執(zhí)行 npm start 再次查看輸出后的 app.js 的代碼片段:
-Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.wordsToSentence = wordsToSentence; +/* harmony export (immutable) */ __webpack_exports__["a"] = wordsToSentence; function wordsToSentence() { ... }
模塊輸出方式又回到了使用 Babel 前的代碼。
js 的環(huán)境似乎已經(jīng)準備就緒,但 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') } ] }, ... };
執(zhí)行 npm start,現(xiàn)在可以看到頁面已經(jīng)有了樣式。但是,我們說過,我們希望使用先進的武器: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') } ] }, ... };
執(zhí)行 npm start,webpack 編譯沒有報錯,頁面顯示一切正常!
代碼自動更新(熱更新)
如果你嘗試修改 index.scss 的樣式,你有沒注意到一個問題:頁面會自動刷新。但有時候我們在開發(fā)一個模塊,比如 dialog,刷新會導致你需要反復的在頁面上操作才能看到這個 dialog 的樣式更新。那我們有沒有辦法不刷新頁面又能看到代碼的更新呢?
其實很簡單,因為 webpack-dev-server 已經(jīng)內(nèi)置了這樣的功能,我們只要配置下 package.json的 npm scripts:
"scripts": { "start": "webpack-dev-server --hot --inline --port 3003" },
注意到上面的代碼,我們增加了 --hot --inline,讓開發(fā)環(huán)境有了熱更新的能力。我們重新執(zhí)行 npm start,然后將你的瀏覽器和編輯器并排放置,然后反復修改 index.scss,你會看到頁面不會刷新,但樣式在自動的推送更新,這就是傳說中的熱更新。
結(jié)束語
到這里,簡單(簡陋)的、現(xiàn)代化的前端開發(fā)環(huán)境已經(jīng)有了基本的雛形,但是,本篇文章不是webpack 的使用指南,也不是 ES6 的語法教程,盡管如此,還是希望你通過本篇文章感受到前端開發(fā)在工程化領(lǐng)域的發(fā)展帶來的驚喜。