webpack學習筆記一

主要參考: https://blog.madewithlove.be/post/webpack-your-bags/css

 

原由:
做爲運維狗, 對前端一竅不通但心嚮往之, 最近作一個Dashboard, 注意到 bootstrap, echarts, vuejs 都提供 npm 的下載, 最開始是手動複製粘貼 min.css, min.js 文件到靜態目錄, 後來以爲這實在是太low了, 而後才知道 webpack 這等神器.html

webpack基本原理前端

import stylesheet from 'styles/my-styles.scss';
import logo from 'img/my-logo.svg';
import someTemplate from 'html/some-template.html';
console.log(stylesheet);  // "body{font-size:12px}"
console.log(logo);        // "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5[...]"
console.log(someTemplate) // "<html><body><h1>Hello</h1></body></html>"

 

自問自答vue

1) 必定要用ES6麼?
答: 你看你們都用了, 對吧
2) styles, img, html 都被轉換成了字符串, 圖啥?
答: webpack藉此構建一個 smart module packing system, 配置得當, 會自動根據頁面需求靈活打包這些 styles, img, html, 取得良好優化還不用操心.
3) webpack是生成新的 js, css 文件, 還只是生成一個模塊, 最後指向被打包的 js, css?
答: 生成新的文件.node

 

從零開始jquery

nvm
最開始使用 brew install node, 而後有個地方不管如何也調試不過, 後來刪了 brew 的 node, 使用 nvm 重裝 node LTS, 調試經過.webpack

$ node -v
v4.4.2
$ npm -v
2.15.0
$ mkdir
app | cd app $ npm init -y $ npm install jquery --save $ npm install webpack --save-dev

最終產品中會用到的用 --save, 僅僅開發過程用到的用 --save-deves6

src/index.jsweb

var $ = require('jquery');
$('body').html('Hello');

app 的入口文件, 講究點的還應該在 package.json 裏面配置一下, 而後可使用 npm build 這些正則表達式

webpack.config.js

module.exports = {
  // entry: __dirname + '/src',
  // entry: './src' 均可以, 由於默認的入口文件就是 index.js
  entry: './src/index.js',
  output: {
    // 輸出目錄, 不存在會自動建立
    path: 'builds',
    // 輸出文件名
    filename: 'bundle.js'
  }
};

配置文件沒有使用ES6, 也見有人用過, 舒服就好

index.html

<!DOCTYPE html>
<html>
<body>
<h1>My title</h1>
<a>Click me</a>
<script src="builds/bundle.js"></script>
</body>
</html>

src連接到builds新生成的文件, 因此若是使用express, 能夠直接用express.static()

$ webpack
Hash: d41fc61f5b9d72c13744
Version: webpack 1.12.14
Time: 301ms
    Asset    Size  Chunks             Chunk Names
bundle.js  268 kB       0  [emitted]  main
   [0] ./src/index.js 53 bytes {0} [built]
    + 1 hidden modules

須要說明一下, 這裏面執行和輸出的代碼我就直接粘原博客了, 到了跑不通的地方再說怎麼改.

這裏能看到 bundle.js 包含了入口文件 /src/index.js 和一個隱藏的模塊

$ webpack --display-modules
bundle.js  268 kB       0  [emitted]  main
   [0] ./src/index.js 53 bytes {0} [built]
   [1] ./~/jquery/dist/jquery.js 259 kB {0} [built]

--display-modules 會顯示出這個隱藏模塊爲jquery.js

 

配置Loader

webpack依靠loader去處理 js, css, scss, html 這些, 第一個要說的是 babel-loader, 負責把 es6 轉換成 es5

$ npm install babel-core babel-preset-es2015 --save-dev
$ npm install babel-loader --save-dev

在項目根分區下創建 .babelrc 文件, babel 依賴它的配置進行轉換, 這裏標記一下, .babelrc 是不是跟 webpack.config.js 同一個文件夾就能夠? 沒有試驗過.

.babelrc { "presets": ["es2015"] }

es2015是es6在2015年發佈的版本.

webpack.config.js

module.exports = {
  // entry: __dirname + '/src',
  // entry: './src' 均可以, 由於默認的入口文件就是 index.js
  entry: './src/index.js',
  output: {
    // 輸出目錄, 不存在會自動建立
    path: 'builds',
    // 輸出文件名
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        // 正則表達式, 匹配到以後就將文件交與 loader 處理
        test: /\.js$/,
        // loader: 'babel-loader' 也能夠, -loader 能夠省略
        loader: 'babel',
        // include 是說對只這個文件夾下的全部文件採用規則
        // 相對應的還有exclude, 默認是項目根分區下的全部文件
        include: __dirname + '/src'
      }
    ]
  }
};

src/index.js

import $ from 'jquery';
$('body').html('Hello');

此時es6就能夠經過編譯了

 

小型組件

$ npm install mustache --save
$ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev

mustache是輕量的模板引擎, 咱們用它來建立一個 Button 組件和一個 Header 組件

webpack.config.js 局部

{
    test:    /\.js$/,
    loader:  'babel',
    include: __dirname + '/src',
},
{
    test:   /\.scss$/,
    loader: 'style!css!sass',
    // 下面也能夠, 我的喜歡 loader, 能體現loader處理的邏輯
    // loaders: ['style', 'css', 'sass'],
},
{
    test:   /\.html$/,
    loader: 'html',
}

src/Components/Button.scss

.button {
  background: tomato;
  color: white;
}

src/Components/Button.html

<a class="button" href="{{link}}">{{text}}</a>

src/Components/Button.js

import $ from 'jquery';
import template from './Button.html';
import Mustache from 'mustache';
import './Button.scss';

export default class Button {
    constructor(link) {
        this.link = link;
    }

    onClick(event) {
        event.preventDefault();
        alert(this.link);
    }

    render(node) {
        const text = $(node).text();

        // Render our button
        $(node).html(
            Mustache.render(template, {text})
        );

        // Attach our listeners
        $('.button').click(this.onClick.bind(this));
    }
}

src/index.js

import Button from './Components/Button';
const button = new Button('google.com');
button.render('a');

執行webpack以後, 使用瀏覽器打開 index.html, 點擊 Chick me, 應該有以下的表現

 

index.html

 

代碼分離

剛瞭解 webpack 時就擔憂, 全部的東西打包成一個巨大的文件, 頁面須要加載多久? 顯然, 我想多了.

import $ from 'jquery';

// This is a split point
require.ensure([], () => {
  // All the code in here, and everything that is imported
  // will be in a separate file
  const library = require('some-big-library');
  $('foo').click(() => library.doSomething());
});

全部 require.ensure 中 require 的文件, 會被打包成一個新的 chunk, 相似於這樣:

bundle.js
|- jquery.js
|- index.js // our main file
chunk1.js
|- some-big-libray.js
|- index-chunk.js // the code in the callback

好比, 設置當界面出現 <a></a> 時就自動加載 Button 組件

src/index.js

if (document.querySelectorAll('a').length) {
    require.ensure([], () => {
        const Button = require('./Components/Button').default();
        const button = new Button('google.com');

        button.render('a');
    });
}

注意require後面的default. http://stackoverflow.com/questions/33704714/cant-require-default-export-value-in-babel-6-x/33705077

由於本質上,export default就是輸出一個叫作default的變量或方法,而後系統容許你爲它取任意名字。http://es6.ruanyifeng.com/#docs/module

$ webpack --display-modules --display-chunks
Hash: 43b51e6cec5eb6572608
Version: webpack 1.12.14
Time: 1185ms
      Asset     Size  Chunks             Chunk Names
  bundle.js  3.82 kB       0  [emitted]  main
1.bundle.js   300 kB       1  [emitted]
chunk    {0} bundle.js (main) 235 bytes [rendered]
    [0] ./src/index.js 235 bytes {0} [built]
chunk    {1} 1.bundle.js 290 kB {0} [rendered]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {1} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {1} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {1} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {1} [built]

能夠看到, 由於 Button 是在 require.ensure 中, 因此對它單獨打包生成了 1.bundle.js, 而 bundle.js 目前只包含一些 webpack 邏輯.

這也就實現了, 靜態資源僅在須要的狀況下加載.

webpack.config.js 局部

path:       'builds',
filename:   'bundle.js',
publicPath: './builds/'

publicPath的做用 http://stackoverflow.com/questions/28846814/what-does-publicpath-in-webpack-do

 

增長一個Header組件

src/Components/Header.scss

.header {
  font-size: 3rem;
}

src/Components/Header.html

<header class="header">{{text}}</header>

src/Components/Header.js

import $ from 'jquery';
import Mustache from 'mustache';
import template from './Header.html';
import './Header.scss';

export default class Header {
    render(node) {
        const text = $(node).text();

        $(node).html(
            Mustache.render(template, {text})
        );
    }
}

src/index.js

// If we have an anchor, render the Button component on it
if (document.querySelectorAll('a').length) {
    require.ensure([], () => {
        const Button = require('./Components/Button');
        const button = new Button('google.com');

        button.render('a');
    });
}

// If we have a title, render the Header component on it
if (document.querySelectorAll('h1').length) {
    require.ensure([], () => {
        const Header = require('./Components/Header');

        new Header().render('h1');
    });
}

執行webpack

$ webpack --display-modules --display-chunks
Hash: 178b46d1d1570ff8bceb
Version: webpack 1.12.14
Time: 1548ms
      Asset     Size  Chunks             Chunk Names
  bundle.js  4.16 kB       0  [emitted]  main
1.bundle.js   300 kB       1  [emitted]
2.bundle.js   299 kB       2  [emitted]
chunk    {0} bundle.js (main) 550 bytes [rendered]
    [0] ./src/index.js 550 bytes {0} [built]
chunk    {1} 1.bundle.js 290 kB {0} [rendered]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {1} {2} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built]
chunk    {2} 2.bundle.js 290 kB {0} [rendered]
    [2] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {1} {2} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built]
    [9] ./src/Components/Header.js 1.62 kB {2} [built]
   [10] ./src/Components/Header.html 64 bytes {2} [built]
   [11] ./src/Components/Header.scss 1.05 kB {2} [built]
   [12] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]

注意到, 兩個 chunk 都包含 jquery, 有沒有一種方法, 把這些共用的資源打包到一塊兒? webpack 提供了大量的 plugins 進行不一樣項目的優化, 這裏用到的是 CommonChunksPlugin

webpack.config.js 局部

var webpack = require('webpack');

module.exports = {
    entry:   './src',
    output:  {
      // ...
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name:      'main', // Move dependencies to our main file 也就是 bundle.js, 能夠取別的名字, 會生成新的js文件
            children:  true, // Look for common dependencies in all children 遍歷
            minChunks: 2, // How many times a dependency must come up before being extracted 重複多少次就抽取出來
        }),
    ],
    module:  {
      // ...
    }
};

再次執行 webpack

chunk    {0} bundle.js (main) 287 kB [rendered]
    [0] ./src/index.js 550 bytes {0} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {0} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {0} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {0} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {0} [built]
chunk    {1} 1.bundle.js 3.28 kB {0} [rendered]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
chunk    {2} 2.bundle.js 2.92 kB {0} [rendered]
    [9] ./src/Components/Header.js 1.62 kB {2} [built]
   [10] ./src/Components/Header.html 64 bytes {2} [built]
   [11] ./src/Components/Header.scss 1.05 kB {2} [built]
   [12] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]

看到, 共用的部分 jquery, mustache, css-loader, style-loader 已經被打包到了 bundle.js 中

 

生產環境中配置

是用 Shell 環境變量 NODE_ENV 來標記是否爲生產環境. 若是是, 進行一系列的優化. 原做者給出了一些最經常使用的 plugins, 業界良心.

webpack.config.js 局部

var webpack    = require('webpack');
var production = process.env.NODE_ENV === 'production';

var plugins = [
    new webpack.optimize.CommonsChunkPlugin({
        name:      'main', // Move dependencies to our main file
        children:  true, // Look for common dependencies in all children,
        minChunks: 2, // How many times a dependency must come up before being extracted
    }),
];

if (production) {
    plugins = plugins.concat([
       // Production plugins go here
    ]);
}

module.exports = {
    // debug 模式會打包更多的代碼以供排錯
    debug: !production,
    // 調試時打包後的代碼與打包前的資源關聯? 須要 sourcemaps, 'eval' 是其中的佼佼者之一
    devtool: production ? false : 'eval',
    entry:   './src',
    output:  {
        path:       'builds',
        filename:   'bundle.js',
        publicPath: 'builds/',
    },
    plugins: plugins,
    // ...
};

plugins

if (production) {
    plugins = plugins.concat([

        // This plugin looks for similar chunks and files
        // and merges them for better caching by the user
        new webpack.optimize.DedupePlugin(),

        // This plugins optimizes chunks and modules by
        // how much they are used in your app
        new webpack.optimize.OccurenceOrderPlugin(),

        // This plugin prevents Webpack from creating chunks
        // that would be too small to be worth loading separately
        new webpack.optimize.MinChunkSizePlugin({
            minChunkSize: 51200, // ~50kb
        }),

        // This plugin minifies all the Javascript code of the final bundle
        new webpack.optimize.UglifyJsPlugin({
            mangle:   true,
            compress: {
                warnings: false, // Suppress uglification warnings
            },
        }),

        // This plugins defines various variables that we can set to false
        // in production to avoid code related to them from being compiled
        // in our final bundle
        new webpack.DefinePlugin({
            __SERVER__:      !production,
            __DEVELOPMENT__: !production,
            __DEVTOOLS__:    !production,
            'process.env':   {
                BABEL_ENV: JSON.stringify(process.env.NODE_ENV),
            },
        }),

    ]);
}

以上的 plugins 能夠提供很多的優化了.

output

output: {
    path:          'builds',
    filename:      production ? '[name]-[hash].js' : 'bundle.js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath:    './builds/',
},

以前生成的文件是 bundle.js 和 1.bundle.js 等, 生產環境中, 能夠改爲hash, 實際上是爲了版本控制, 不過沒有實際用過

一天 webpack 下來, 在 builds 中會生成不少文件, 在生產環境中能夠配置, 每次生成以前, 先清空 builds

$ npm install clean-webpack-plugin --save-dev

webpack.config.js 局部

var webpack     = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');

// ...

if (production) {
    plugins = plugins.concat([

        // Cleanup the builds/ folder before
        // compiling our final assets
        new CleanPlugin('builds'),

分別執行以查看效果

$ webpack
                bundle.js   314 kB       0  [emitted]  main
1-21660ec268fe9de7776c.js  4.46 kB       1  [emitted]
2-fcc95abf34773e79afda.js  4.15 kB       2  [emitted]

$ NODE_ENV=production webpack
main-937cc23ccbf192c9edd6.js  97.2 kB       0  [emitted]  main

注意到, 在生產環境中, 生成的文件被有效的壓縮了

 

單獨打包css文件

你會發現, js 和 css 文件被打包到一塊兒了, 出於某些我還不能體會的緣由, css 文件有單獨打包的需求.

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

webpack.config.js 局部

var webpack    = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractPlugin = require('extract-text-webpack-plugin');
var production = process.env.NODE_ENV === 'production';

var plugins = [
    new ExtractPlugin('bundle.css'), // <=== where should content be piped
    new webpack.optimize.CommonsChunkPlugin({
        name:      'main', // Move dependencies to our main file
        children:  true, // Look for common dependencies in all children,
        minChunks: 2, // How many times a dependency must come up before being extracted
    }),
];

// ...

module.exports = {
    // ...
    plugins: plugins,
    module:  {
        loaders: [
            {
                test:   /\.scss$/,
                loader: ExtractPlugin.extract('style', 'css!sass'),
            },
            // ...
        ],
    }
};

注意到, scss 的 loader 被修改了.

src/styles.css

body {
  font-family: sans-serif;
  background: darken(white, 0.2);
}

src/index.js 局部

import './styles.scss';

// Rest of our file

執行webpack

$ webpack
                bundle.js    318 kB       0  [emitted]  main
1-a110b2d7814eb963b0b5.js   4.43 kB       1  [emitted]
2-03eb25b4d6b52a50eb89.js    4.1 kB       2  [emitted]
               bundle.css  59 bytes       0  [emitted]  main

發現生成了獨立的 bundle.css 文件, 文件名能夠經過修改 ExtractTextPlugin('bundle.css', {allChunks: true}) 控制, {allChunks: true} 是指將全部 chunks 中的 css 文件都抽取出來.

 

BUG!!!

注意到, 若是此時執行 NODE_ENV=production webpack 就會有報錯, 而原做者估計並無測試. 簡言之, 多是由於 ExtractTextPlugin doesn't work with either MinChunkSizePlugin nor AggressiveMergingPlugin. 而解決的辦法是 As a temporary workaround, add the following line after line 21 in extract-text-webpack-plugin/index.js: if (typeof c === 'undefined') return;

https://www.bountysource.com/issues/27317053-error-when-using-with-minchunksizeplugin

 

處理圖片

建立圖片目錄 img/puppy.jpg

puppy

 

src/styles.scss

body {
    font-family: sans-serif;
    background: darken(white, 0.2);
    background-image: url('../img/puppy.jpg');
    background-size: cover;
}

須要 file-loader 和 url-loader, 前者會返回圖片的 URL, 後者會將圖片進行 base64 編碼 data:image/jpeg;base64

$ npm install url-loader file-loader --save-dev

增長 loader

{
    test:   /\.(png|gif|jpe?g|svg)$/i,
    loader: 'url?limit=10000',
},

limit = 10000 是說, 若是圖片大小大於 10KB, 那麼返回 URL, 若是小於 10KB, 則進行 base64 編碼

執行 webpack

                bundle.js   15 kB       0  [emitted]  main
1-b8256867498f4be01fd7.js  317 kB       1  [emitted]
2-e1bc215a6b91d55a09aa.js  317 kB       2  [emitted]
               bundle.css  2.9 kB       0  [emitted]  main

用瀏覽器打開 index.html, 頁面應該長這樣

index.html

 

PS:

以後還有 webpack-dev-server 的內容, 不過對我來講暫時不須要了, 手動刷新就夠了, 時間比較緊. 

由於是使用 vue, vue 有官方的 vue-cli, 是能夠直接生成項目的. 

相關文章
相關標籤/搜索