你一定喜歡看的 Webpack 2.× 入門實戰(zhàn)

SpringBride

最近在學習 Webpack,網(wǎng)上大多數(shù)入門教程都是基于 Webpack 1.x 版本的,我學習 Webpack 的時候是看了 zhangwang<<入門 Webpack,看這篇就夠了>> 寫的非常好,不過是基于 Webpack 1.x 版本的,語法上和 Webpack 2.x 有一點不同.我學習時是使用 Webpack 2.6.1 版本,所以我就尋思著基于 zhangwang<<入門 Webpack,看這篇就夠了>> 寫下這篇 Webpack 2.x 的入門實戰(zhàn),是我學習 Webpack 的記錄.聽說 Webpack 3.x 版本快要出了,不得不感嘆前端領域發(fā)展的真是太快了!

Webpack 是什么?

Webpack 是前端資源模塊化管理和打包工具。

Webpack 可以將許多松散的模塊按照依賴和規(guī)則打包成符合生產(chǎn)環(huán)境部署的前端資源。

Webpack 可以將按需加載的模塊進行代碼分隔,等到實際需要的時候再異步加載。

Webpack 通過 loader 的轉換,任何形式的資源都可以視作模塊,比如 CommonJs 模塊、 AMD 模塊、 ES6 模塊、CSS、圖片、 JSON、Coffeescript、 LESS、 SASS 等。

一圖勝千言,下圖足夠說明上面巴巴拉拉一大堆是啥了!

bundle your assets

對于模塊的組織,通常有如下幾種方法:

  • 分開寫幾個 js 文件,使用 script 標簽加載.
  • CommonJS 進行同步加載, Node.js 就使用這種方式.
  • AMD進行異步加載, require.js 使用這種方式.
  • 新的 ES6 模塊.

Webpack 的特點

  • 豐富的插件,流行的插件都有,方便進行開發(fā)工作.
  • 大量的加載器,便于處理和加載各種靜態(tài)資源.
  • 將按需加載的模塊進行代碼分隔,等到實際需要的時候再異步加載.

Webpack 的優(yōu)勢

  • Webpack 以 commonJS 的形式來書寫腳本,但對 AMD / CMD / ES6 模塊 的支持也很全面,方便舊項目進行代碼遷移。
  • 所有資源都能模塊化。
  • 開發(fā)便捷,能替代部分 Grunt / Gulp 的工作,比如打包、壓縮混淆、圖片轉 base64、SASS 解析成 CSS 等。
  • 擴展性強,插件機制完善,特別是支持模塊熱替換(見 模塊熱替換 )的功能讓人眼前一亮。

Webpack 與 Grunt / Gulp

在沒有學習 Webpack 之前我對 Webpack、Grunt、Gulp 的認識很模糊,只知道好像這三個東西都是前端自動化工具,都是用來使前端自動化、模塊化、工程化的,這三者是可以替代彼此的前端工具.

其實 Webpack 和 Gulp / Grunt 并沒有太多的可比性,Gulp / Grunt 是一種能夠優(yōu)化前端開發(fā)流程的自動化工具,而 Webpack 是一種模塊化的解決方案,不過 Webpack 的優(yōu)點使得 Webpack 可以替代 Gulp / Grunt 一部分工作。

Grunt / Gulp 的工作方式是:在一個配置文件中,指明對某些文件需要進行哪些處理,例如:編譯、組合、壓縮等任務的具體步驟,Grunt / Gulp 之后可以自動替你完成這些任務。Grunt / Gulp的工作流程如下圖:

Gulp / Grunt

Webpack 的工作方式是:把你的項目當做一個整體,通過一個給定的主文件( 如:index.js ),Webpack 將從這個文件開始找到你的項目的所有依賴文件,使用 loaders 處理它們,最后打包為一個瀏覽器可識別的 JavaScript 文件。Webpack工作方式如下圖:

Webpack

如果實在要進行比較,Webpack 的處理速度更快更直接,因為 Webpack 的歷史包袱小.Webpack 還能打包更多不同類型的文件。

開始使用 Webpack

初步了解 Webpack 后,就可以開始學習使用 Webpack。這里會以一個小的 Demo 為例子來一步一步進行動手學習!

新建 Webpack 項目

1. 新建一個文件夾,命名為 webpack-demo,webpack-demo 就是你的項目名,項目名建議使用小寫字母,并且不帶空格,不能含有大寫字母.

2. 安裝 Webpack,Webpack 可以使用 npm 安裝,如果你還不知道 npm 為何物,請 Google,也可以參考 Node.js 安裝配置NPM 使用介紹快速了解、安裝 npm.

使用終端在該文件夾中執(zhí)行下述指令就可以完成安裝,由于網(wǎng)絡原因安裝過程可能需要一些時間。

//全局安裝
npm install -g webpack
//安裝到你的項目目錄
npm install --save-dev webpack

Webpack 可以全局安裝,也可以安裝到你的項目目錄.剛開始學習 Webpack 為了方便,建議同時全局安裝和安裝到你的項目目錄.

3. 在 webpack-demo 文件夾中創(chuàng)建一個 package.json 文件,這是一個標準的 npm 說明文件,里面蘊含了豐富的信息,包括當前項目的依賴模塊,自定義的腳本任務等等。在終端中使用 npm init 命令可以自動創(chuàng)建這個 package.json 文件.

npm init

輸入這個命令后,終端會問你一系列諸如項目名稱,項目版本,項目描述,入口文件,作者等信息,不過不用擔心,如果你不準備在 npm 中發(fā)布你的模塊,這些問題的答案都不重要,回車默認即可.這些信息今后都可以更改 package.json 來修改,所以不用擔心.

4. 在 webpack-demo 文件夾中創(chuàng)建兩個文件夾 app 文件夾和 public 文件夾, app 文件夾用來存放原始數(shù)據(jù),例如: SASS 文件、LESS 文件、JavaScript 模塊等,public 文件夾用來存放經(jīng)過 Webpack 處理過的 app 文件夾數(shù)據(jù),這也是準備給瀏覽器讀取的數(shù)據(jù),其中包括使用 Webpack 打包后的 js 文件等。在這里還需要在 public 文件夾中創(chuàng)建 index.html 文件.在 app 文件夾中創(chuàng)建 Greeter.js 和 main.js 文件,此時項目結構如下圖所示:

項目結構

5. 在 public 文件夾中的 index.html 文件只有最基礎的 html 代碼,它唯一的目的就是加載打包后的 js 文件 bundle.js.

// index.html

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>webpack-demo</title>
</head>

<body>
    <div id='root'>
    </div>
    <script type="text/javascript" src="bundle.js"></script>
</body>

</html>

6. 在 app 文件夾中的 Greeter.js 只包括一個用來返回問候信息的 html 元素的函數(shù)。

// Greeter.js

module.exports = function() {
    var greet = document.createElement('div');
    greet.textContent = "Hi there and greetings!";
    return greet;
}

7. 在 app 文件夾中的 main.js 用來把 Greeter 模塊(其實可以簡單的把它看作 Greeter.js)返回的節(jié)點插入頁面。

// main.js

var greeting = require('./Greeter.js');
document.getElementById('root').appendChild(greeting());

Webpack 配置文件

Webpack 配置文件其實也是一個簡單的 JavaScript 模塊,可以把所有與項目構建相關的信息放在里面。在 webpack-demo 文件夾根目錄下新建一個名為 webpack.config.js 的文件,并在其中進行最簡單的配置.如下所示,它包含入口文件路徑和存放打包后文件的地方路徑。

// webpack.config.js

module.exports = {
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    }
}

注:__dirname 是 node.js 中的一個全局變量,它指向當前 js 文件所在的目錄.

現(xiàn)在只需要在終端里運行 webpack 命令就可以了,這條命令會自動參考 webpack.config.js 文件中的配置選項打包你的項目,輸出結果如下:

終端結果

此時項目的 public 文件夾下也會出現(xiàn)打包好的 bundle.js 文件.此時項目結構如下圖所示:

項目結構

可以看出 webpack 同時編譯了 main.js 和 Greeter.js,打開 public 目錄下的 index.html 文件,就可以看到最終效果,如下圖:

最終效果

利用 npm 更快捷的執(zhí)行打包任務

通過 Webpack 配置文件和執(zhí)行 webpack 命令其實是比較煩人且容易出錯的,不過值得慶幸的是 npm 可以引導任務執(zhí)行,對其進行配置后可以使用簡單的 npm start 命令來代替這些繁瑣的命令。在 package.json 中對 npm 的腳本部分進行相關設置,相當于把 npm 的 start 命令指向 webpack 命令,設置方法如下:

// package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "webpack"
    },
    "author": "",
    "license": "ISC"
}

執(zhí)行 npm start 后命令行的輸出顯示:

npmStartTermialResult

現(xiàn)在只需要使用 npm start 就可以打包文件了.打開 public 目錄下的 index.html 文件,看到的最終效果是不是與之前的一樣.

利用 Webpack 生成 Source Maps

簡單說,Source Maps 就是一個信息文件,里面儲存著位置信息。也就是說,轉換后的代碼的每一個位置,所對應的轉換前的位置.有了它,出錯的時候,除錯工具將直接顯示原始代碼,而不是轉換后的代碼。這無疑給開發(fā)者帶來了很大方便.為了方便調(diào)試可以利用 Webpack 生成 Source Maps.

在 Webpack 的配置文件中配置 Source Maps,需要配置 devtool,它有以下四種不同的配置選項,各有優(yōu)缺點,描述如下:

  • source-map 在一個單獨的文件中產(chǎn)生一個完整且功能完全的文件。這個文件具有最方便調(diào)試的 Source Maps,但是這個文件會比較大,會減慢打包文件的構建速度.
  • cheap-module-source-map 在一個單獨的文件中生成一個不帶列映射的 Source Maps,不帶列映射能夠提高項目構建速度,但這也使得瀏覽器開發(fā)者工具只能對應到具體的行,不能對應到具體的列,會對調(diào)試造成不便.
  • eval-source-map 在同一個文件中生成干凈的完整的 Source Maps。這個選項可以在不影響構建速度的前提下生成完整的 Source Maps,但是對打包后輸出的 js 文件的執(zhí)行具有性能和安全的隱患。不過在開發(fā)階段這是一個非常好的選項,但是在生產(chǎn)階段一定不要用這個選項.
  • cheap-module-eval-source-map 這是在打包文件時最快的生成 Source Maps 的方法,生成的Source Map 會和打包后的 js 文件同行顯示,沒有列映射,和 eval-source-map 選項具有相似的缺點,文件的執(zhí)行具有性能和安全的隱患.

上述選項由上到下打包速度越來越快,不過同時也具有越來越多的負面作用,較快的構建速度的后果就是對打包的文件執(zhí)行有一定影響。在學習階段以及在小到中型的項目上,eval-source-map是一個很好的選項,不過記得只在開發(fā)階段使用它.

編輯 webpack-demo 文件夾下的 webpack.config.js 文件配置 devtool 選項,生成 Source Maps 文件.配置 devtool 后的 webpack.config.js 文件如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    }
}

執(zhí)行 npm start 命令后就能生成對應的 Source Maps,終端輸出信息如下圖:

終端輸出信息

此時項目中 public 文件夾下也生成了名為 bundle.js.map 的 Source Maps 文件.此時項目結構如下圖所示:

項目結構

使用 Webpack 構建本地服務器

想不想讓你的瀏覽器監(jiān)測你修改的代碼,并自動刷新修改后的結果.其實 Webpack 提供一個可選的本地開發(fā)服務器,這個本地服務器基于 node.js 構建,可以實現(xiàn)你想要的這些功能,不過它是一個單獨的組件,在 Webpack 中進行配置之前需要單獨安裝它作為項目依賴.在終端中輸入下面的指令安裝對應組件.建議同時全局安裝和安裝到你的項目目錄.

//全局安裝
npm install -g webpack-dev-server
//安裝到你的項目目錄
npm install --save-dev webpack-dev-server

devserver 作為 Webpack 配置選項中的一項,具有以下配置選項

  • contentBase 默認 webpack-dev-server 會為根文件夾提供本地服務器,如果想為另外一個目錄下的文件提供本地服務器,應該在這里設置其所在目錄(本例設置到“public"文件夾下).
  • port 設置默認監(jiān)聽端口,如果省略,默認為"8080".
  • inline 設置為 true,當源文件改變時會自動刷新頁面.
  • historyApiFallback 在開發(fā)單頁應用時非常有用,它依賴于 HTML5 history API,如果設置為 true,所有的跳轉將指向 index.html.

編輯 webpack-demo 文件夾下的 webpack.config.js 文件配置以上選項.

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    }
}

在終端中輸入如下命令,構建本地服務器:

webpack-dev-server

終端輸出信息如下圖,表示 Webpack 構建的本地服務器已啟動.

終端輸出信息

在瀏覽器中打開 http://localhost:9000/ 就可以看到像之前一樣的問候語頁面.

結果

也可以使用 npm 更快捷的執(zhí)行任務,編輯 webpack-demo 文件夾下的 package.json 文件 scripts 選項.

// package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "webpack",
        "dev": "webpack-dev-server --devtool eval --progress --content-base build"
    },
    "author": "",
    "license": "ISC"
}

在終端中執(zhí)行 npm run dev 命令,輸出信息如下圖,一樣可以啟動的本地服務器.

image.png

Ctrl + C 即可退出本地服務器.

一切皆模塊

Webpack 有一個不可不說的優(yōu)點,它把所有的文件都可以當做模塊處理,包括你的 JavaScript 代碼,也包括 CSS 和 fonts 以及圖片等等,只要通過合適的 Loaders,它們都可以被當做模塊被處理.

Loaders

webpack 可以使用 loader 來預處理文件。這允許你打包除 JavaScript 之外的任何靜態(tài)資源.通過使用不同的 loader,Webpack 通過調(diào)用外部的腳本或工具可以對任何靜態(tài)資源進行處理,比如說分析 JSON 文件并把它轉換為 JavaScript 文件,或者說把 ES6 / ES7 的 JS 文件轉換為現(xiàn)代瀏覽器可以識別的 JS 文件.對 React 開發(fā)而言,合適的 Loaders 可以把 React 的 JSX 文件轉換為 JS 文件.

Loaders 需要單獨安裝并且需要 在webpack.config.js 下的 modules 關鍵字下進行配置,Loaders 的配置選項包括以下幾方面:

  • test:一個匹配 Loaders 所處理的文件的拓展名的正則表達式(必須)
  • loader:loader 的名稱(必須)
  • include/exclude: 手動添加必須處理的文件/文件夾,或屏蔽不需要處理的文件/文件夾(可選)
  • query:為 Loaders 提供額外的設置選項(可選)

繼續(xù)動手實踐,修改 app 文件夾下的 Greeter.js 文件,把問候消息放在一個單獨的 JSON 文件里,通過 loader 的使 Greeter.js 可以讀取該 JSON 文件.

1. 在 app 文件夾下創(chuàng)建 config.json 文件,并輸入如下代碼:

//config.json

{
    "greetText": "Hi there and greetings from JSON!"
}

2. 編輯 app 文件夾下的 Greeter.js 文件,修改后如下:

// Greeter.js

var config = require('./config.json');

module.exports = function() {
    var greet = document.createElement('div');
    greet.textContent = config.greetText;
    return greet;
}

3. 安裝支持導入 JSON 文件的 json-loader .由于 webpack 2.× 默認支持導入 JSON 文件.如果你使用自定義文件擴展名,可能仍然需要使用此 loader.在終端中運行如下命令,安裝 json-loader 到你的項目中.

//安裝到你的項目中
npm install --save-dev json-loader

因為 json-loader 安裝到你的項目目錄里了,所以 webpack-demo 項目下會新增一個 node_modules 文件夾用于存放安裝的 json-loader.此時的項目結構如下:

項目結構

4. 編輯 webpack.config.js 文件配置 modules 選項,添加 json-loader,編輯后的文件如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }]
    }
}

在終端中輸入 npm start 重新編譯打包,再在瀏覽器中打開 public 文件夾下的 index.html 文件,如果看到和下圖一樣的,就說明 json-loader 配置成功了.

index.html

Babel

Babel 其實是一個編譯 JavaScript 的平臺,它的強大之處表現(xiàn)在可以通過編譯幫你達到以下目的:

  • 把 ES6 / ES7 標準的 JavaScript 轉化為瀏覽器能夠解析的 ES5 標準的 JavaScript.
  • 使用基于 JavaScript 進行了拓展的語言,比如 React 的 JSX.

Babel的安裝與配置

Babel 其實是幾個模塊化的包,其核心功能位于稱為 babel-core 的 npm 包中,不過 Webpack 把它們整合在一起使用,但是對于每一個你需要的功能或拓展,你都需要安裝單獨的包.用得最多的是解析 ES6 的 babel-preset-es2015 包和解析 JSX 的 babel-preset-react 包.

先來安裝這些依賴包,輸入如下命令,把這些依賴包安裝到你的項目中.

// 利用 npm 一次性安裝多個依賴模塊,模塊之間用空格隔開
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react

//安裝 React 和 React-DOM
npm install --save react react-dom

編輯 webpack.config.js 文件配置 modules 選項,添加 Babel 配置,編輯后的文件如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader",
            query: {
                presets: ['es2015', 'react']
            }
        }]
    }
}

使用 ES6 的語法,更新 app 文件夾下的 Greeter.js 文件,并返回一個 React 組件,修改后的代碼如下:

// Greeter.js

import React, { Component } from 'react';
import config from './config.json';

class Greeter extends Component {
    render() {
        return (<div> { config.greetText } </div>);
        }
    }

    export default Greeter;

使用 ES6 的模塊定義和渲染 Greeter 模塊,修改 app 文件夾下的 main.js 文件,修改后的代碼如下:

// main.js

import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';

render(<Greeter />, document.getElementById('root'));

在終端中運行 npm start 命令重新編譯打包,終端輸出信息如下:

終端輸出信息

在瀏覽器中打開 public 文件夾下的 index.html 文件,如果看到和下圖一樣的,就說明已經(jīng)成功配置了 Babel.

index.html

Babel的配置選項

Babel 其實可以完全在 webpack.config.js 文件中進行配置,但是考慮到 babel 具有非常多的配置選項,在單一的 webpack.config.js 文件中進行配置往往使得這個文件顯得太復雜,因此一些開發(fā)者支持把 babel 的配置選項放在一個單獨的名為 ".babelrc" 的配置文件中。我們現(xiàn)在的 babel 的配置并不算復雜,不過之后我們會再加一些東西,因此現(xiàn)在我們就提取出相關部分,分兩個配置文件進行配置, Webpack 會自動調(diào)用 .babelrc 里的 babel 配置選項.

編輯 webpack.config.js 文件配置 modules 選項,添加 Babel 配置,編輯后的文件如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }]
    }
}

在 webpack-demo 文件夾下新建 .babelrc 文件,添加如下代碼:

// .babelrc

{
    "presets": ['es2015', 'react']
}

在終端中運行 npm start 命令重新編譯打包,終端輸出信息如下:

終端輸出信息

在瀏覽器中打開 public 文件夾下的 index.html 文件,如果看到和下圖一樣的,就說明已經(jīng)成功配置了 Babel.

index.html

CSS

Webpack 提供兩個工具處理樣式表,css-loader 和 style-loader.

  • css-loader 使你能夠使用類似 @import 和 url(...) 的方法實現(xiàn) require() 的功能
  • style-loader 將所有的計算后的樣式加入頁面中

二者組合在一起使你能夠把樣式表嵌入 Webpack 打包后的 JS 文件中。

先來安裝 css-loader, style-loader,輸入如下命令,把這些依賴包安裝到你的項目中.

npm install --save-dev style-loader css-loader

編輯 webpack.config.js 文件配置 modules 選項,添加處理樣式表配置,編輯后的文件如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader' //添加對樣式表的處理,感嘆號的作用在于使同一文件能夠使用不同類型的 loader
        }]
    }
}

接下來,在 app 文件夾里創(chuàng)建一個名為 main.css 的文件,在文件中添加如下代碼,對一些元素設置樣式.

// main.css

html {
    box-sizing: border-box;
    -ms-text-size-adjust: 100%;
    -webkit-text-size-adjust: 100%;
}

*,
*:before,
*:after {
    box-sizing: inherit;
}

body {
    margin: 0;
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6,
p,
ul {
    margin: 0;
    padding: 0;
}

Webpack 只有單一的入口,其它的模塊需要通過 import, require, url 等導入相關位置,為了讓 Webpack 能找到 main.css 文件,我們把它導入 app 文件夾下的 main.js 中.修改 app 文件夾下的 main.js 文件,修改后的代碼如下:

// main.js

import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
import './main.css'; //導入css文件

render(<Greeter/>, document.getElementById('root'));

在終端中運行 npm start 命令重新編譯打包,終端輸出信息如下:

終端輸出信息

在瀏覽器中打開 public 文件夾下的 index.html 文件,如果看到和下圖一樣的,就說明已經(jīng)配置成功了.

index.html

通常情況下,css 會和 js 打包到同一個文件中,并不會打包為一個單獨的 css 文件,不過通過合適的配置 Webpack 也可以把 css 打包為單獨的文件的。
不過這也只是 Webpack 把 css 當做模塊而已,繼續(xù)看一個真的 CSS 模塊的實踐.

CSS module

在過去的一些年里,JavaScript 通過一些新的語言特性、更好的工具、更好的實踐方法(比如說模塊化)發(fā)展得非常迅速。模塊使得開發(fā)者把復雜的代碼轉化為小的、干凈的、依賴聲明明確的單元,且基于優(yōu)化工具,依賴管理和加載管理可以自動完成。

不過前端的另外一部分,CSS 的發(fā)展就相對慢一些,大多的樣式表卻依舊是巨大且充滿了全局類名,這使得維護和修改都非常困難和復雜。

CSS modules 的技術就能夠把 JS 的模塊化思想帶入 CSS 中來,通過 CSS 模塊,所有的類名,動畫名默認都只作用于當前模塊.

Webpack 從一開始就對 CSS 模塊化提供了支持,在 CSS loader 中進行配置后,你所需要做的一切就是把 modules 傳遞到需要的地方,然后就可以直接把 CSS 的類名傳遞到組件的代碼中,且這樣做只對當前組件有效,不必擔心在不同的模塊中具有相同的類名可能會造成的問題。

編輯 webpack.config.js 文件配置 modules 選項,添加處理樣式表配置,編輯后的文件如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules' //跟前面相比就在后面加上了 ?modules
        }]
    }
}

接下來,在 app 文件夾里創(chuàng)建一個名為 Greeter.css 的文件,在文件中添加如下代碼,對一些元素設置樣式.

// Greeter.css

.root {
    background-color: #eee;
    padding: 10px;
    border: 3px solid #ccc;
}

導入 .root 到 Greeter.js 中,修改 app 文件夾下的 Greeter.js 文件,修改后的代碼如下:

// Greeter.js

import React, { Component } from 'react';
import config from './config.json';
import styles from './Greeter.css'; //導入 .root 到 Greeter.js 中

class Greeter extends Component {
    render() {
        return ( <div className={styles.root}> { config.greetText } </div>);
        }
    }

    export default Greeter;

在終端中運行 npm start 命令重新編譯打包,終端輸出信息如下:

終端輸出信息

在瀏覽器中打開 public 文件夾下的 index.html 文件,如果看到和下圖一樣的,就說明已經(jīng)配置成功了.

index.html

CSS modules 也是一個很大的主題,有興趣的話可以去官方文檔查看更多消息.下面兩篇文章也可以看看:

CSS 預處理器

CSS 預處理器可以將 SASS、LESS 文件轉化為瀏覽器可識別的 CSS 文件,以下是常用的CSS 預處理器 loaders.

  • Less Loader
  • Sass Loader
  • Stylus Loader

其實也存在一個 CSS 的處理平臺 PostCSS,它可以幫助你的 CSS 實現(xiàn)更多的功能,可以看看<<PostCSS 是個什么鬼東西?>>.

舉例來說如何使用 PostCSS,我們使用 PostCSS 來為 CSS 代碼自動添加適應不同瀏覽器,不同版本的 CSS 前綴。首先安裝 postcss-loader 和 autoprefixer(自動添加前綴的插件),安裝到你的項目中.

npm install --save-dev postcss-loader autoprefixer

編輯 webpack.config.js 文件配置 modules 選項,添加 postcss-loader 處理樣式表配置,編輯后的文件如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在后面加上了 !postcss-loader
        }]
    }
}

在 webpack-demo 文件夾下新建 postcss.config.js 文件,添加如下代碼:

// postcss.config.js

module.exports = {
    plugins: [
        //調(diào)用autoprefixer插件,還可以配置選項添加需要兼容的瀏覽器版本.
        require("autoprefixer")({ browsers: ['ie>=8', '>1% in CN'] })
    ]
}

現(xiàn)在你寫的樣式會自動根據(jù) Can i use 里的數(shù)據(jù)添加不同前綴了.在終端中運行 npm start 命令重新編譯打包,終端輸出信息如下:

終端輸出信息

在瀏覽器中打開 public 文件夾下的 index.html 文件,如果看到和下圖一樣的,就說明已經(jīng)成功配置了 PostCSS.

index.html

插件(Plugins)

插件(Plugins)是用來拓展 Webpack 功能的,它會在整個構建過程中生效,執(zhí)行相關的任務。
Loaders 和 Plugins 常常被弄混,但是他們其實是完全不同的東西,可以這么說,Loaders 是在打包構建過程中用來處理源文件的(JSX,Scss,Less..),一次處理一個;插件并不直接操作單個文件,它直接對整個構建過程其作用。

Webpack 有很多內(nèi)置插件,同時也有很多第三方插件,可以讓我們完成更加豐富的功能。

使用插件的方法

要使用某個插件,我們需要通過 npm 安裝它,然后要做的就是在 Webpack 配置中的 Plugins 關鍵字部分添加該插件的一個實例.

編輯 webpack.config.js 文件配置 Plugins 選項,添加一個實現(xiàn)版權聲明的 BannerPlugin 插件,BannerPlugin 是內(nèi)置插件不需要使用 npm 安裝.編輯后的文件如下:

// webpack.config.js

var webpack = require("webpack");
module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/public/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在后面加上了 !postcss-loader
        }]
    },
    plugins: [
        new webpack.BannerPlugin("Copyright Flying Unicorns inc.")//在這個數(shù)組中new一個實例就可以了
    ]
}

在終端中運行 npm start 命令重新編譯打包,終端輸出信息如下:

終端輸出信息

在瀏覽器中打開 public 文件夾下的 index.html 文件,如果看到和下圖一樣的,就說明已經(jīng)成功配置了 BannerPlugin 插件.

index.html

常用插件

給大家推薦幾個常用的插件

HtmlWebpackPlugin

這個插件的作用是依據(jù)一個簡單的模板,幫你生成最終的 html 文件,這個文件中自動引用了你打包后的 JS 文件。每次編譯都在文件名中插入一個不同的哈希值。

安裝 HtmlWebpackPlugin 到你的項目中
npm install --save-dev html-webpack-plugin

在使用 HtmlWebpackPlugin 之前,需要對 webpack-demo 項目結構做一些改變.

1. 移除 public 文件夾.

2. 在 app 目錄下,創(chuàng)建一個文件名為 index.tmpl.html 模板文件,在編譯過程中,HtmlWebpackPlugin 插件會依據(jù)此模板生成最終的 html 頁面,會自動添加所依賴的 css、 js、favicon等文件.index.tmpl.html 模板文件代碼如下:

// index.tmpl.html

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>webpack-demo</title>
</head>

<body>
    <div id="root"></div>
</body>

</html>

3. 在 webpack-demo 文件夾下新建一個 build 文件夾用來存放最終的輸出文件.

4. 編輯 webpack.config.js 文件配置 Plugins 選項,添加 HtmlWebpackPlugin 插件,修改 output 選項.編輯后的文件如下:

// webpack.config.js

var webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/build/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在后面加上了 !postcss-loader
        }]
    },
    plugins: [
        new webpack.BannerPlugin("Copyright Flying Unicorns inc."), //在這個數(shù)組中new一個實例就可以了
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new一個插件的實例,并傳入相關的參數(shù)
        })
    ]
}

此時項目結構如下圖所示:

項目結構

在終端中運行 npm start 命令重新編譯打包,終端輸出信息如下:

終端輸出信息

此時項目結構已經(jīng)發(fā)生改變,build 文件夾下存放了最終的輸出的文件,項目結構如下圖所示:

項目結構

在瀏覽器中打開 build 文件夾下的 index.html 文件,如果看到和下圖一樣的,就說明已經(jīng)成功配置了 HtmlWebpackPlugin 插件.

index.html

Hot Module Replacement

Hot Module Replacement(HMR)也是 Webpack 里很有用的一個插件,它允許你在修改組件代碼后,自動刷新實時預覽修改后的效果。
在 Webpack 中使用 HMR 也很簡單,只需要做兩項配置.

  • 在 Webpack 配置文件中添加 HMR 插件
  • 在 Webpack Dev Server 中添加 hot 參數(shù)

不過配置完這些后,JS 模塊其實還是不能自動熱加載的,還需要在你的 JS 模塊中執(zhí)行一個 Webpack 提供的 API 才能實現(xiàn)熱加載,雖然這個 API 不難使用,但是如果是 React 模塊,使用我們已經(jīng)熟悉的 Babel 可以更方便的實現(xiàn)功能熱加載。

整理下思路,具體實現(xiàn)方法如下

  • Babel 和 Webpack 是獨立的工具,二者可以一起工作,二者都可以通過插件拓展功能.
  • HMR 是一個 Webpack 插件,它讓你能瀏覽器中實時觀察模塊修改后的效果,但是如果你想讓它工作,需要對模塊進行額外的配額.
  • Babel 有一個叫做 react-transform-hrm 的插件,可以在不同 React 模塊進行額外的配置下讓 HMR 正常工作.

編輯 webpack.config.js 文件配置 Plugins / devServer 選項,添加 Hot Module Replacement 插件.編輯后的文件如下:

// webpack.config.js

var webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/build/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        port: "9000",
        inline: true,
        historyApiFallback: true,
        hot: true
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在后面加上了 !postcss-loader
        }]
    },
    plugins: [
        new webpack.BannerPlugin("Copyright Flying Unicorns inc."), //在這個數(shù)組中new一個實例就可以了
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new一個插件的實例,并傳入相關的參數(shù)
        }),
        new webpack.HotModuleReplacementPlugin() //熱加載插件
    ]
}

安裝 react-transform-hmr 插件

npm install --save-dev babel-plugin-react-transform react-transform-hmr

編輯在 webpack-demo 文件夾下的 .babelrc 文件,編輯后的文件如下:

// .babelrc

{
  "presets": ["react", "es2015"],
  "env": {
    "development": {
    "plugins": [["react-transform", {
       "transforms": [{
         "transform": "react-transform-hmr",
         "imports": ["react"],
         "locals": ["module"]
       }]
     }]]
    }
  }
}

編輯 webpack-demo 文件夾下的 package.json 文件 scripts 選項,添加 --hot 選項開啟代碼熱替換.

// package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "webpack",
        "dev": "webpack-dev-server --devtool eval --progress --content-base build --hot"
    },
    "author": "",
    "license": "ISC"
}

在終端中執(zhí)行 npm run dev 命令,輸出信息如下圖,一樣可以啟動自動熱加載.

終端輸出信息

在瀏覽器中打開 http://localhost:9000/ 就可以看到像之前一樣的問候語頁面.

結果

現(xiàn)在當你使用 React 時,就可以熱加載模塊了.按 Ctrl + C 即可退出自動熱加載.

產(chǎn)品階段的構建

我們已經(jīng)使用 Webpack 構建了一個完整的開發(fā)環(huán)境.但是在產(chǎn)品階段,可能還需要對打包的文件進行額外的處理,比如說優(yōu)化、壓縮、緩存以及分離 CSS 和 JS.

對于復雜的項目來說,需要復雜的配置,這時候分解配置文件為多個小的文件可以使得事情井井有條,以 webpack-demo 項目來說,我們在 webpack-demo 文件夾下創(chuàng)建一個名為 webpack.production.config.js 的文件,在里面加上基本的配置代碼,如下:

// webpack.production.config.js

var webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/build/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在后面加上了 !postcss-loader
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new一個插件的實例,并傳入相關的參數(shù)
        })
    ]
}

編輯 webpack-demo 文件夾下的 package.json 文件 scripts 選項,添加 build 選項,編輯后的文件如下:

// package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "webpack",
        "dev": "webpack-dev-server --devtool eval --progress --content-base build --hot",
        "build": "webpack --config ./webpack.production.config.js --progress"
    },
    "author": "",
    "license": "ISC"
}

在終端中執(zhí)行 npm run build 命令,輸出信息如下圖:

輸出信息

說明分解配置文件為多個小的文件成功了.

優(yōu)化插件

Webpack 提供了一些在發(fā)布階段非常有用的優(yōu)化插件,它們大多來自于 Webpack 社區(qū),可以通過 npm 安裝,通過以下插件可以完成產(chǎn)品發(fā)布階段所需的功能.

  • OccurrenceOrderPlugin: 為組件分配 ID,通過這個插件 Webpack 可以分析和優(yōu)先考慮使用最多的模塊,并為它們分配最小的 ID.
  • UglifyJsPlugin:壓縮JS代碼.
  • ExtractTextPlugin:分離 CSS 和 JS 文件.

我們來看看如何使用它們,OccurrenceOrderPlugin 和 UglifyJS plugins 都是內(nèi)置插件,我們只需要安裝 ExtractTextPlugin 插件.

安裝 ExtractTextPlugin 插件

npm install --save-dev extract-text-webpack-plugin

編輯 webpack.config.js 文件配置 Plugins 選項,添加這三個插件,因為要分離 css 所以還要配置 module 下的 loaders 選項.編輯后的文件如下:

// webpack.config.js

var webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/build/", //存放打包后文件的地方路徑
        filename: "bundle.js" //打包后的文件名
    },
    devServer: {
        port: "9000",
        inline: true,
        historyApiFallback: true,
        hot: true
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
                fallback: "style-loader",
                use: "css-loader?modules!postcss-loader"
            })
        }]
    },
    plugins: [
        new webpack.BannerPlugin("Copyright Flying Unicorns inc."), //在這個數(shù)組中new一個實例就可以了
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new一個插件的實例,并傳入相關的參數(shù)
        }),
        new webpack.HotModuleReplacementPlugin(), //熱加載插件
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin(),
        new ExtractTextPlugin("style.css")
    ]
}

在終端中執(zhí)行 npm start 命令,輸出信息如下圖:

輸出信息

此時項目結構已經(jīng)發(fā)生改變,build 文件夾下多出了抽離出來的 style.css 文件還有對應的 style.css.map 文件,項目結構如下圖所示:

項目結構

如果你打開 build 文件夾下的 bundle.js 文件,就可以看到 bundle.js 文件內(nèi)容已經(jīng)被壓縮處理了.

說明這三個插件已經(jīng)配置成功了.

緩存

為了加快加載速度,合理的利用緩存是必不可少的.使用緩存的最好方法是保證文件名和文件內(nèi)容是匹配的.內(nèi)容改變,名稱也相應改變.

Webpack 可以把一個哈希值添加到打包文件的文件名中,添加特殊的字符串混合體([name], [id] and [hash])到輸出文件名前,便于修改 BUG 以后,對應更新用戶本地的緩存文件.

編輯 webpack.config.js 文件修改 output / plugins 選項.編輯后的文件如下:

// webpack.config.js

var webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口文件路徑
    output: {
        path: __dirname + "/build/", //存放打包后文件的地方路徑
        filename: "[name]-[hash].js" //打包后的文件名
    },
    devServer: {
        port: "9000",
        inline: true,
        historyApiFallback: true,
        hot: true
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 文件夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
                fallback: "style-loader",
                use: "css-loader?modules!postcss-loader"
            })
        }]
    },
    plugins: [
        new webpack.BannerPlugin("Copyright Flying Unicorns inc."), //在這個數(shù)組中new一個實例就可以了
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new一個插件的實例,并傳入相關的參數(shù)
        }),
        new webpack.HotModuleReplacementPlugin(), //熱加載插件
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin(),
        new ExtractTextPlugin("[name]-[hash].css")
    ]
}

在終端中執(zhí)行 npm start 命令,輸出信息如下圖:

輸出信息

此時項目 build 文件夾下的 main.css 和 main.js 文件都對應的加上了哈希值.項目結構如下圖所示:

項目結構

如果你打開 build 文件夾下的 index.html 文件,就可以看到引用的 css、js 文件名也對應發(fā)生了改變,這樣修改 BUG 以后,也能對應更新用戶本地的緩存文件.

進階,永不止步

其實到這里我的這篇 Webpack 2.x 的入門實戰(zhàn)已經(jīng)完結了!但這也只是個入門而已!在實際項目中運用還是不夠的,還有很多細節(jié)我并沒深入講,所以大家還想進階的話建議好好去看看 webpack-china 的文檔.

另外實戰(zhàn)項目 webpack-demo 的源碼,我已經(jīng)放到 Github 上去了,歡迎大家提意見.

還有一點我覺得很重要,要學會看控制臺輸出信息,能夠看控制臺輸出信息解決的問題,就不要上 Google 搜了!

鳴謝

這篇 Webpack 2.x 的入門實戰(zhàn)是基于 zhangwang<<入門Webpack,看這篇就夠了>> 寫出來的,是我學習 Webpack 的實戰(zhàn)記錄.特別感謝 zhangwang 付出,如果你覺得這篇文章對你有幫助,請轉到 zhangwang 為他點個贊.

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 無意中看到zhangwnag大佬分享的webpack教程感覺受益匪淺,特此分享以備自己日后查看,也希望更多的人看到...
    小小字符閱讀 8,379評論 7 35
  • GitChat技術雜談 前言 本文較長,為了節(jié)省你的閱讀時間,在文前列寫作思路如下: 什么是 webpack,它要...
    蕭玄辭閱讀 12,912評論 7 110
  • 在現(xiàn)在的前端開發(fā)中,前后端分離、模塊化開發(fā)、版本控制、文件合并與壓縮、mock數(shù)據(jù)等等一些原本后端的思想開始...
    Charlot閱讀 5,676評論 1 32
  • 1. 新建一個文件夾,命名為 webpack-cli , webpack-cli 就是你的項目名,項目名建議使用小...
    魯大師666閱讀 1,669評論 1 3
  • 公休去哪里玩?。磕銕自路菪菹。课覀儼炎o照、通行證都辦了吧!最近我們的話題中多了“旅行”兩個字。 自從前段時間最流...
    有一只小熊閱讀 284評論 0 1

友情鏈接更多精彩內(nèi)容