工欲善其事,必先利其器。單頁面應用的開發和生產環境涉及檔案的編譯、壓縮、打包、合併等,目前前端最流行的莫過於 webpack
。為了深入瞭解 webpack
以及其相關外掛,我們決定不採用腳手架,自己來搭建基於 webpack
的開發和生產環境。
基礎環境
nodejs的安裝: 移步官網
建議使用nvm來管理nodejs的版本
安裝nvm
Webpack相關plugin、loader的介紹
我們使用的是 webpack@2.X.X
,建議讀完 官方文件 對她有個大概的瞭解。
-
webpack-dev-server 用
webpack-dev-server
來進行開發環境下面的自動打包編譯,包括熱更新等等。當然也可以自己通過webpack-dev-middleware
來自定義一個開發伺服器。具體可以參考webpack-dev-server
的原始碼。 -
webpack-hot-middleware 向入口檔案中新增一個
client
檔案,當檔案變化時,伺服器端可以通過socket
事件來通知這個client
來實現熱更新。注:這個事件是 EventSource事件。webpack-dev-server
已經將這個中介軟體封裝到內部,我們只需要進行配置即可。 -
babel-loader 編譯
es6
程式碼。移步官網。值得一提的是es2016
,es2017
,env
等是對已經或者將要被加入 JS這門語言的提案進行預編譯,而stage-0
,stage-1
,stage-2
等是對將來可能加入立案裡面的語法的預編譯。 -
extract-text-webpack-plugin 樣式檔案預設會被 webpack 打包到js檔案中。這個外掛可以提取出這些被打包進入的檔案。
當然我們用到的不只是這些,你可以到npm官網或者github上面找到這些plugin、loader的詳細用法
初始目錄結構
blog
├─ dist # 輸出目錄
├─ task # 這裡來放webpack處理和配置檔案
├─ src
| ├─ components # 元件
| └ index.js # 入口檔案
| package.json
跑通開發環境
在 src/
目錄下新建入口檔案 index.js
import React from `react`
import ReactDOM from `react-dom`
// 這裡需要藉助 webpack 的同名功能來代替繁瑣的相對路徑
import HomeComponent from `components/Home`
ReactDOM.render(<HomeComponent />, document.getElementById(`root`))
在 src/component/
目錄下新建 Home.js
模組
import React, { Component } from `react`
export default class Home extends Component {
render(){
return <div>Hello world!</div>
}
}
在 src/
目錄下新建 index.html
檔案來作為單頁面的HTML檔案
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>blog</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
現在我們來新增 webpack
的配置檔案來對這些檔案進行打包、編譯
在 task/
目錄下新建 config.js
檔案
const path = require(`path`)
const webpack = require(`webpack`)
const webpackMerge = require(`webpack-merge`)
const HtmlWebpackPlugin = require(`html-webpack-plugin`)
const base = {
// 上下文環境,相對路徑都基於這個路徑
context: path.resolve(__dirname, `..`),
entry: `./src/index.js`,
output: {
path: path.resolve(__dirname, `../dist`),
publicPath: `/assets/`,
filename: `[name].js`
},
module: {
rules: [
{
test: /.js$/,
include: path.resolve(__dirname, `../src`),
exclude: [ /node_modules/ ],
use: [
`react-hot-loader`,
{
loader: `babel-loader`,
options: {
presets: [`env`, `react`],
plugins: [
"add-module-exports",
"transform-runtime"
]
}
}
],
}
]
},
resolve: {
extensions: [`.js`, `.jsx`],
alias: {
// 這裡對應著入口檔案中 component 的同名配置
components: path.resolve(__dirname, `../src/components`)
}
}
}
const dev = webpackMerge(base, {
output: {
publicPath: `/`
},
// 原始檔的 source map
devtool: "source-map",
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
`process.env`: {
NODE_ENV: `"development"`
}
}),
new HtmlWebpackPlugin({
template: `./src/index.html`,
filename: `index.html`,
inject: true
})
],
devServer: {
// 開啟熱載入,需要 hmr 的支援
hot: true,
contentBase: path.resolve(__dirname, `../dist`),
// 這個路徑一定要和 output 的 publicPath 的屬性一致
publicPath: `/`,
}
})
const prod = webpackMerge(base, {
})
// 根據 NODE_ENV 來決定輸出的配置
module.exports = process.env.NODE_ENV === `production` ? prod : dev
在專案跟路徑下執行
export NODE_ENV=development && webpack-dev-server --config ./task/config.js
export NODE_ENV=development
設定 NODE_ENV
這個環境變數為 development
有助於我們區分開發環境和生產環境。這是mac下面的設定方法,windows 可以自行搜尋
編譯成功,並且頁面輸出 Hello world!
表示配置跑通…
在 package.json
新增
{
"scripts": {
"dev": "export NODE_ENV=development && webpack-dev-server --config ./task/config.js --progress --colors --hotOnly"
}
}
TROUBLESHOOTING
通過 webpack-merge
來覆蓋 output.publicPath
屬性
如果 devServer.publicPath = output.publicPath = `/assets/`
的話,那麼在瀏覽器中開啟 localhost:80XX/assets/index.html
才能訪問到 index.html
檔案,而將 publicPath = `/`
就直接通過 localhost:80XX
就可以訪問。
loader
的配置
其中 include
, exclude
就不多說
{
use: [
`react-hot-loader`,
{
loader: `babel-loader`,
options: {
presets: [`env`, `react`],
plugins: [
"add-module-exports",
"transform-runtime"
]
}
}
],
}
react-hot-loader 會解決更改 react 元件的時 webpack 熱更新直接重新整理頁面的問題。
babel-presets-react
用來處理 react 的 jsx 語法
babel-plugin-add-module-exports babel@6
會將es6的語法
// home.js
export default `foo`
轉化為
`use strict`;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = `foo`;
所以在使用 commonjs
的語法 require(`./home`)
時,得到的是{default: `foo`}
,所以:
var home = require(`./home`).default
console.log(home) // `foo`
這個外掛可以避免這一現象
html-webpack-plugin
這裡解釋一下使用 html-webpack-plugin
的必要性。
其實完全可以扔一個靜態的 index.html
給 webpack-dev-server
,這裡面只需要有個 <script src="bundle.js"></script>
標籤,就可以成功來進行訪問。但是更多的時候我們會定製打包檔案的名稱: output.filename: `[name]-[hash].js`
,編譯之後 webpack
會輸出一個類似於這樣的檔案 bundle-fb9758acf17b2b5fb653.js
,那麼你每次打包都需要去更改那個src屬性,而 html-webpack-plugin
可以幫你解決這些事情
新增對樣式檔案的支援
在 src/component/
下新建 Home.module.less
@color: green;
:global {
body {
background-color: red;
}
}
.wrap {
color: @color;
}
在同級目錄下的 Home.js
元件中引入這個 less 檔案
import React, { Component } from `react`
import Style from `./home.module.less`
export default class Home extends Component {
render(){
return <div className={Style.wrap}>Hello world!</div>
}
}
我們沒有在 task/config.js
下增加對 less
檔案的支援,肯定會報錯的。先說一下為什麼要以 .module.less
標識 less
檔案,只會我們會引入 babel-plugin-import
對樣式庫 antd
進行按需載入,由於 antd
原始碼中的樣式檔案也是用 less
寫的,這樣會導致這些檔案被作為 css-module
處理,所以加以區別,這是參考 atool-build 的配置。
在 task/config.js
檔案新增 :
const ExtractTextPlugin = require(`extract-text-webpack-plugin`)
const autoprefixer = require(`autoprefixer`)
const runsack = require(`rucksack-css`)
const theme = require(`../theme.js`)()
const postcssPlugins = () => [
runsack(), // 可選
autoprefixer({
browsers: [`last 2 versions`, `Firefox ESR`, `> 1%`, `ie >= 8`, `iOS >= 8`, `Android >= 4`],
}),
]
// 在base.module.rules裡增加
{
test: /.module.less$/,
loader: ExtractTextPlugin.extract({
fallback: `style-loader`, // 將下面處理過的檔案插入html中
use: [
{
loader: `css-loader`,
// 開啟對css-module的支援,並定義className的輸出格式
options: { modules: true, importLoaders: 1, localIdentName: `[name]__[local]___[hash:base64:5]`}
},
{
loader: `postcss-loader`,
options: {
plugins: postcssPlugins
}
},
{
loader: `less-loader`,
// 覆蓋預設的全域性配置
options: {"modifyVars": theme}
}
]
})
},
// 在 base.plugins 裡增加
new ExtractTextPlugin({
filename: `css/[name]-[hash].css`,
}),
其他的 loader 和 webpack plugin 就不再贅述, 移步文件
還要注意的是
less-loader
中的配置選項,{"modifyVars": theme}
,這可以覆蓋less
檔案的配置,可以用來自定義樣式庫antd
, 繼續檢視
在專案根目錄下新建 theme.js
module.exports = function(){
return {}
}
別忘記安裝配置檔案裡面用到的 loader、pulgins… = =
安裝 less-loader
記得把 less
也裝上,它的文件也是有強調的哦!
小結
基礎的環境配置就到這裡。生產環境你可以自行配置,之後我會在後面的文章中列出來。
你可以在這個倉庫檢視 這第一次 commit 哦!下面皆可以愉快的做自己的部落格了!