【單頁面部落格從前端到後端】環境搭建

whistlem發表於2019-02-16

工欲善其事,必先利其器。單頁面應用的開發和生產環境涉及檔案的編譯、壓縮、打包、合併等,目前前端最流行的莫過於 webpack 。為了深入瞭解 webpack 以及其相關外掛,我們決定不採用腳手架,自己來搭建基於 webpack 的開發和生產環境。

基礎環境

nodejs的安裝: 移步官網

建議使用nvm來管理nodejs的版本
安裝nvm

Webpack相關plugin、loader的介紹

我們使用的是 webpack@2.X.X ,建議讀完 官方文件 對她有個大概的瞭解。

  • webpack-dev-serverwebpack-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.htmlwebpack-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 哦!下面皆可以愉快的做自己的部落格了!

相關文章