1. 程式人生 > >使用NPM + Webpack進行前端開發的示例

使用NPM + Webpack進行前端開發的示例

最近在使用Webpack來幫助進行前端開發,覺得這個東西的確挺好用。所以用一個簡單的示例記錄一下,供他人以及自己以後的參考。

這篇文章不是Webpack的教程貼,也不分析Webpack的優缺點,只是簡單的記錄我自己覺得還不錯的一套用法,在閱讀本文前,讀者需要對NPM以及Webpack有個基礎的瞭解。

1.專案結構


專案名稱叫webpack-demo。

原始碼目錄是app,下面有三個資料夾,js,less(當然你也可以使用postcss或者sass等),以及html。

package.json是npm中的包管理配置檔案,webpack.config.js是webpack預設的配置檔案,webpack.plugin.js則是我為了使webpack.config.js看起來更清晰而提取出的一些配置內容,顧名思義是提取出了外掛的配置。

node_modules是執行npm install後依賴包的安裝目錄。

build資料夾則是最終專案編譯後的結果,其中的JS和CSS是webpack根據原始碼中的依賴自動生成的一個個bundle檔案,而html則是原封不動的從app中複製過去的。

二、配置分析

1.package.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "product": "webpack",
    "dev": "webpack"
  },
  "dependencies": {
    "jquery": "^3.1.0"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^0.1.10",
    "copy-webpack-plugin": "^3.0.1",
    "css-loader": "^0.23.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "less": "^2.7.1",
    "less-loader": "^2.2.3",
    "style-loader": "^0.13.1",
    "webpack": "^1.13.1",
    "webpack-merge": "^0.14.1"
  }
}

這裡面主要的就是scripts和兩個dependencies。之所以要分product和dev兩個script,是用來區分生產和開發兩種環境,這關係到webpack中的配置。

兩個dependencies的內容就是開發和執行時所依賴的包,對這兩個東西不太清楚的讀者可以參考stackoverflow上的回答

2.webpack配置

webpack.config.js

/**
 * Created by hshen on 8/15/16.
 */

// Define paths
const path = require('path');
const PATHS = {
    app: path.join(__dirname, 'app'),
    build: path.join(__dirname, 'build')
};

// A tool to merge config object
const merge = require('webpack-merge');

// Configuration for plugins
const plugins = require('./webpack.plugins');

// Common configuration for production and dev
const common = merge(
        {
            entry: {
                page_a: path.join(PATHS.app,'js','pages','page_a.js'),
                page_b: path.join(PATHS.app,'js','pages','page_b.js')
            },
            output: {
                path: path.join(PATHS.build,'js'),
                filename: '[name].js',
                chunkFilename: '[chunkhash].js'
            },
            resolve: {
                root: [
                    PATHS.app
                ],
                alias: {
                    js: 'js',
                    css: 'less'
                },
                extensions: ['', '.js']
            }
        },
        plugins.clean(PATHS.build),
        plugins.copy(),
        plugins.extractCommon('common.js'),
        plugins.less()
);

var config = null;

// Detect the branch where npm is running on
switch(process.env.npm_lifecycle_event) {
    case 'product':
        config = merge(
            common,
            plugins.minify()
        );
        break;

    case 'dev':
    default:
        config = merge(
            common,
            // Set source map for debug
            {
                devtool: 'source-map'
            }
        );
        break;
}

module.exports = config;
根據註釋大概可以知道配置是怎麼工作的: dev和product打包都需要的操作: 對每個頁面的入口檔案及其依賴打包成一個bundle;對路徑做一些alias,方便js中的依賴;提取各個頁面的公共依賴作為common;複製html;編譯less。(當然還可以加上jshint等更多的功能) 對於dev,加上sourcemap用於debug;對於product,做minify的處理。 為了使配置結構更清晰,我提取了外掛配置的部分到單獨的webpack.plugins.js中。

webpack.plugins.js

/**
 * Created by hshen on 8/15/16.
 */
const webpack = require('webpack');

exports.minify = function () {
    return {
        plugins: [
            new webpack.optimize.UglifyJsPlugin({
                // Don't beautify output (enable for neater output)
                beautify: false,
                // Eliminate comments
                comments: false,
                compress: {
                    warnings: false,
                    // Drop `console` statements
                    drop_console: true
                }
            })
        ]
    };
}

// Clean a specific folder
exports.clean = function (path) {
    const CleanWebpackPlugin = require('clean-webpack-plugin');

    return  {
        plugins: [
            new CleanWebpackPlugin([path], {
                // Without `root` CleanWebpackPlugin won't point to our
                // project and will fail to work.
                root: process.cwd()
            })
        ]
    };
}

exports.extractCommon = function (name) {
    return {
        plugins: [
            new webpack.optimize.CommonsChunkPlugin(name)
        ]
    };
}

exports.copy = function () {
    const path = require('path');
    const PATHS = {
        app: path.join(__dirname, 'app'),
        build: path.join(__dirname, 'build')
    };
    const CopyWebpackPlugin = require('copy-webpack-plugin');

    return {
        plugins: [
            new CopyWebpackPlugin([
                { from: path.join(PATHS.app,'html'), to: path.join(PATHS.build,'html')},
            ], {
                ignore: [

                ],
                // By default, we only copy modified files during
                // a watch or webpack-dev-server build. Setting this
                // to `true` copies all files.
                copyUnmodified: true
            })
        ]
    };
}

exports.less = function () {
    var ExtractTextPlugin = require("extract-text-webpack-plugin");
    var extractLESS = new ExtractTextPlugin('../css/[name].css',{allChunks: true});

    return {
        module: {
            loaders: [
                {
                    test: /\.less$/,
                    exclude: /node_modules/,
                    loader:  extractLESS.extract("style-loader", "css-loader!less-loader") }
            ]
        },
        plugins: [
            extractLESS
        ]
    };
};

三、原始檔分析

a.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Webpack demo a</title>
    <link href="../css/page_a.css" rel="stylesheet" type="text/css">
</head>
<body>

<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../js/page_a.js"></script>
</body>
</html>
對於所有的html而言,都只需要通用的common.js以及每個頁面自己單獨的page_x.js。 因為html是直接被複制過去的,所以這裡引的css和js都是編譯和打包之後的(當然,如果對效能要求不高,可以直接用less-loader而不用extract-text外掛,這樣的話css就會直接內嵌到js中)。

page_a.js

/**
 * Created by hshen on 8/15/16.
 */

var $ = require('jquery');
var component = require('js/components/ComponentA');
require('css/page_a.less');

$('body').append(component);
這裡因為配過了alias,所以就不用寫醜陋的../之類的相對路徑了。 為了不讓css內嵌在js中,使瀏覽器可以平行載入js和css,從而提高效能,我使用了extract-text外掛。 用extract-text外掛的一個壞處: 我除了在html中手動去引css,還要在js中去引編譯前的less。這樣很不利於程式碼的維護。如果有讀者知道怎麼解決這個問題,請不吝賜教!

四、為什麼不一起使用Bower、Grunt等

對於包管理,我覺得NPM一個就足夠了,再額外使用Bower有點多此一舉,另外還增加了配置的複雜度。 對於Grunt等Task Runner,Webpack本身已經有足夠多的外掛可以讓我們去做很多事情,再把Grunt加上去也會增加複雜度。對於外掛沒有提供的東西,其實自己寫點NPM的script也是可以解決的。 當然我也很期待讀者能夠給我不一樣的看法!