Skip to content

Latest commit

 

History

History
1012 lines (798 loc) · 24.5 KB

webpack-demo.md

File metadata and controls

1012 lines (798 loc) · 24.5 KB

Webpack入门示例库

一个常见的Webpack配置文件

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

module.exports = {
  entry: __dirname + "/app/main.js",
  output: {
    path: __dirname + "/build",
    filename: "[name]-[hash].js"
  },

  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: "json"
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel'
      },
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract('style', 'css?modules!postcss')
      }
    ]
  },
  postcss: [
    require('autoprefixer')
  ],

  plugins: [
    new HtmlWebpackPlugin({
      template: __dirname + "/app/index.tmpl.html"
    }),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin(),
    new ExtractTextPlugin("[name]-[hash].css")
  ]
}

什么是WebPack,为什么要使用它?

为什要使用WebPack

现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法

  • 模块化,让我们可以把复杂的程序细化为小的文件;
  • 类似于TypeScript这种在JavaScript基础上拓展的开发语言:使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能能装换为JavaScript文件使浏览器可以识别;
  • Scss,less等CSS预处理器
  • ...

这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常反锁的,这就为WebPack类的工具的出现提供了需求。

什么是Webpack

WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。

WebPack和Grunt以及Gulp相比有什么特性

其实Webpack和另外两个并没有太多的可比性,Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack可以替代Gulp/Grunt类的工具。

Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,这个工具之后可以自动替你完成这些任务。Grunt和Gulp的工作流程:

img1

Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个浏览器可识别的JavaScript文件。Webpack工作方式:

img2

How to use

First, install Webpack and webpack-dev-server globally.

$ npm i -g webpack webpack-dev-server

Problem:

zhangjundeMacBook-Pro:webpack-demo zhangjun$ npm install
npm WARN deprecated [email protected]: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
(node:72145) Warning: Possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit
(node:72145) Warning: Possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit
(node:72145) Warning: Possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit
npm WARN deprecated [email protected]: Deprecated.

Fix:

zhangjundeMacBook-Pro:demo01 zhangjun$ npm update -g [email protected]
zhangjundeMacBook-Pro:demo01 zhangjun$ npm update -g [email protected]
zhangjundeMacBook-Pro:demo01 zhangjun$ npm install

Then, clone the repo and install the dependencies.

$ git clone [email protected]:zhangjunhd/react-examples.git
$ cd webpack-demo
$ npm install

Now, play with the source files under the repo's demo* directories.

$ cd demo01
$ webpack-dev-server

Visit http://127.0.0.1:8080 with your browser.

Index

  1. Entry file
  2. Multiple entry files
  3. Babel-loader
  4. CSS-loader
  5. Image loader
  6. CSS Module
  7. UglifyJs Plugin
  8. HTML Webpack Plugin and Open Browser Webpack Plugin
  9. Environment flags
  10. Code splitting
  11. Code splitting with bundle-loader
  12. Common chunk
  13. Vendor chunk
  14. Exposing Global Variables
  15. Hot Module Replacement
  16. React router

Demo01: Entry file (source)

Entry file is a file which Webpack will read to build bundle.js.

For example, main.js is an entry file.

// main.js
document.write('<h1>Hello World</h1>');

index.html

<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

Webpack follows webpack.config.js to build bundle.js.

// webpack.config.js
module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};

Launch the server, visit http://127.0.0.1:8080 .

$ webpack-dev-server

Demo02: Multiple entry files (source)

Multiple entry files are allowed. It is useful for a multi-page app.

// main1.js
document.write('<h1>Hello World</h1>');

// main2.js
document.write('<h2>Hello Webpack</h2>');

index.html

<html>
  <body>
    <script src="bundle1.js"></script>
    <script src="bundle2.js"></script>
  </body>
</html>

webpack.config.js

module.exports = {
  entry: {
    bundle1: './main1.js',
    bundle2: './main2.js'
  },
  output: {
    filename: '[name].js'
  }
};

Demo03: Babel-loader (source)

Loaders are preprocessors which transform a resource file of your app (more info). For example, Babel-loader can transform JSX/ES6 file into JS file. Official doc has a complete list of loaders.

main.jsx is a JSX file.

const React = require('react');
const ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.querySelector('#wrapper')
);

index.html

<html>
  <body>
    <div id="wrapper"></div>
    <script src="bundle.js"></script>
  </body>
</html>

webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        loader: 'babel-loader?presets[]=es2015&presets[]=react'
      },
    ]
  }
};

In webpack.config.js, module.loaders field is used to assign loaders. The above snippet uses babel-loader which also needs plugins babel-preset-es2015 and babel-preset-react to transpile ES6 and React. You can also take another way to set the babel query option.

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loader: 'babel',
      query: {
        presets: ['es2015', 'react']
      }
    }
  ]
}

Demo04: CSS-loader (source)

Webpack allows you to require CSS in JS file, then preprocessed CSS file with CSS-loader.

main.js

require('./app.css');

app.css

body {
  background-color: blue;
}

index.html

<html>
  <head>
    <script type="text/javascript" src="bundle.js"></script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      { test: /\.css$/, loader: 'style-loader!css-loader' },
    ]
  }
};

Attention, you have to use two loaders to transform CSS file. First is CSS-loader to read CSS file, and another is Style-loader to insert Style tag into HTML page. Different loaders are linked by exclamation mark(!).

After launching the server, index.html will have internal style sheet.

<head>
  <script type="text/javascript" src="bundle.js"></script>
  <style type="text/css">
    body {
      background-color: blue;
    }
  </style>
</head>

Demo05: Image loader (source)

Webpack could also require images in JS files.

main.js

var img1 = document.createElement("img");
img1.src = require("./small.png");
document.body.appendChild(img1);

var img2 = document.createElement("img");
img2.src = require("./big.png");
document.body.appendChild(img2);

index.html

<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }
    ]
  }
};

url-loader transforms image files. If the image size is smaller than 8192 bytes, it will be transformed into Data URL; otherwise, it will be transformed into normal URL. As you see, question mark(?) is used to pass parameters into loaders.

After launching the server, small.png and big.png will have the following URLs.

<img src="data:image/png;base64,iVBOR...uQmCC">
<img src="4853ca667a2b8b8844eb2693ac1b2578.png">

Demo06: CSS Module (source)

css-loader?modules (the query parameter modules) enables the CSS Modules spec.

It means your module's CSS is local scoped CSS by default. You can switch it off with :global(...) for selectors and/or rules. (more info)

index.html

<html>
<body>
  <h1 class="h1">Hello World</h1>
  <h2 class="h2">Hello Webpack</h2>
  <div id="example"></div>
  <script src="./bundle.js"></script>
</body>
</html>

app.css

.h1 {
  color:red;
}

:global(.h2) {
  color: blue;
}

main.jsx

var React = require('react');
var ReactDOM = require('react-dom');
var style = require('./app.css');

ReactDOM.render(
  <div>
    <h1 className={style.h1}>Hello World</h1>
    <h2 className="h2">Hello Webpack</h2>
  </div>,
  document.getElementById('example')
);

webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react']
        }
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader?modules'
      }
    ]
  }
};

Launch the server.

$ webpack-dev-server

Visit http://127.0.0.1:8080 , you'll find that only second h1 is red, because its CSS is local scoped, and both h2 is blue, because its CSS is global scoped.

Demo07: UglifyJs Plugin (source)

Webpack has a plugin system to expand its functions. For example, UglifyJs Plugin will minify output(bundle.js) JS codes.

main.js

var longVariableName = 'Hello';
longVariableName += ' World';
document.write('<h1>' + longVariableName + '</h1>');

index.html

<html>
<body>
  <script src="bundle.js"></script>
</body>
</html>

webpack.config.js

var webpack = require('webpack');
var uglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new uglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
};

After launching the server, main.js will be minified into following.

var o="Hello";o+=" World",document.write("<h1>"+o+"</h1>")

Demo08: HTML Webpack Plugin and Open Browser Webpack Plugin (source)

This demo shows you how to load 3rd-party plugins.

html-webpack-plugin could create index.html for you, and open-browser-webpack-plugin could open a new browser tab when Webpack loads.

main.js

document.write('<h1>Hello World</h1>');

webpack.config.js

var HtmlwebpackPlugin = require('html-webpack-plugin');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new HtmlwebpackPlugin({
      title: 'Webpack-demos',
      filename: 'index.html'
    }),
    new OpenBrowserPlugin({
      url: 'http://localhost:8080'
    })
  ]
};

Run webpack-dev-server.

$ webpack-dev-server

Now you don't need to write index.html by hand and don't have to open browser by yourself. Webpack did all these things for you.

Demo09: Environment flags (source)

You can enable some codes only in development environment with environment flags.

main.js

document.write('<h1>Hello World</h1>');

if (__DEV__) {
  document.write(new Date());
}

index.html

<html>
<body>
  <script src="bundle.js"></script>
</body>
</html>

webpack.config.js

var webpack = require('webpack');

var devFlagPlugin = new webpack.DefinePlugin({
  __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
});

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [devFlagPlugin]
};

Now pass environment variable into webpack.

# Linux & Mac
$ env DEBUG=true webpack-dev-server

# Windows
$ set DEBUG=true
$ webpack-dev-server

Demo10: Code splitting (source)

For big web apps it’s not efficient to put all code into a single file, Webpack allows you to split them into several chunks. Especially if some blocks of code are only required under some circumstances, these chunks could be loaded on demand.

At first, you use require.ensure to define a split point. (official document)

// main.js
require.ensure(['./a'], function(require) {
  var content = require('./a');
  document.open();
  document.write('<h1>' + content + '</h1>');
  document.close();
});

require.ensure tells Webpack that ./a.js should be separated from bundle.js and built into a single chunk file.

// a.js
module.exports = 'Hello World';

Now Webpack takes care of the dependencies, output files and runtime stuff. You don't have to put any redundancy into your index.html and webpack.config.js.

<html>
  <body>
    <script src="bundle.js"></script>
  </body>
</html>

webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};

Launch the server.

$ webpack-dev-server

On the surface, you won't feel any differences. However, Webpack actually builds main.js and a.js into different chunks(bundle.js and 1.bundle.js), and loads 1.bundle.js from bundle.js when on demand.

Demo11: Code splitting with bundle-loader (source)

Another way of code splitting is using bundle-loader.

// main.js

// Now a.js is requested, it will be bundled into another file
var load = require('bundle-loader!./a.js');

// To wait until a.js is available (and get the exports)
//  you need to async wait for it.
load(function(file) {
  document.open();
  document.write('<h1>' + file + '</h1>');
  document.close();
});

require('bundle-loader!./a.js') tells Webpack to load a.js from another chunk.

Now Webpack will build main.js into bundle.js, and a.js into 1.bundle.js.

Demo12: Common chunk (source)

When multi scripts have common chunks, you can extract the common part into a separate file with CommonsChunkPlugin.

// main1.jsx
var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>Hello World</h1>,
  document.getElementById('a')
);

// main2.jsx
var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h2>Hello Webpack</h2>,
  document.getElementById('b')
);

index.html

<html>
  <body>
    <div id="a"></div>
    <div id="b"></div>
    <script src="init.js"></script>
    <script src="bundle1.js"></script>
    <script src="bundle2.js"></script>
  </body>
</html>

webpack.config.js

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
  entry: {
    bundle1: './main1.jsx',
    bundle2: './main2.jsx'
  },
  output: {
    filename: '[name].js'
  },
  module: {
    loaders:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react']
        }
      },
    ]
  },
  plugins: [
    new CommonsChunkPlugin('init.js')
  ]
}

Demo13: Vendor chunk (source)

You can also extract the vendor libraries from a script into a separate file with CommonsChunkPlugin.

main.js

var $ = require('jquery');
$('h1').text('Hello World');

index.html

<html>
  <body>
    <h1></h1>
    <script src="vendor.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>

webpack.config.js

var webpack = require('webpack');

module.exports = {
  entry: {
    app: './main.js',
    vendor: ['jquery'],
  },
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin(/* chunkName= */'vendor', /* filename= */'vendor.js')
  ]
};

If you want a module available as variable in every module, such as making $ and jQuery available in every module without writing require("jquery"). You should use ProvidePlugin (Official doc).

// main.js
$('h1').text('Hello World');


// webpack.config.js
var webpack = require('webpack');

module.exports = {
  entry: {
    app: './main.js'
  },
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
      "window.jQuery": "jquery"
    })
  ]
};

Demo14: Exposing global variables (source)

If you want to use some global variables, and don't want to include them in the Webpack bundle, you can enable externals field in webpack.config.js (official document).

For example, we have a data.js.

var data = 'Hello World';

We can expose data as a global variable.

// webpack.config.js
module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react']
        }
      },
    ]
  },
  externals: {
    // require('data') is external and available
    //  on the global var data
    'data': 'data'
  }
};

Now, you require data as a module variable in your script. but it actually is a global variable.

// main.jsx
var data = require('data');
var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>{data}</h1>,
  document.body
);

Demo15: Hot Module Replacement (source)

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.

You have two ways to enable Hot Module Replacement with the webpack-dev-server.

(1) Specify --hot and --inline on the command line

$ webpack-dev-server --hot --inline

Meaning of the options:

  • --hot: adds the HotModuleReplacementPlugin and switch the server to hot mode.
  • --inline: embed the webpack-dev-server runtime into the bundle.
  • --hot --inline: also adds the webpack/hot/dev-server entry.

(2) Modify webpack.config.js.

  • add new webpack.HotModuleReplacementPlugin() to the plugins field
  • add webpack/hot/dev-server and webpack-dev-server/client?http://localhost:8080 to the entry field

webpack.config.js looks like the following.

var webpack = require('webpack');
var path = require('path');

module.exports = {
  entry: [
    'webpack/hot/dev-server',
    'webpack-dev-server/client?http://localhost:8080',
    './index.js'
  ],
  output: {
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
  module: {
    loaders: [{
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      query: {
        presets: ['es2015', 'react']
      },
      include: path.join(__dirname, '.')
    }]
  }
};

Now launch the dev server.

$ webpack-dev-server

Visiting http://localhost:8080, you should see 'Hello World' in your browser.

Don't close the server. Open a new terminal to edit App.js, and modify 'Hello World' into 'Hello Webpack'. Save it, and see what happened in the browser.

App.js

import React, { Component } from 'react';

export default class App extends Component {
  render() {
    return (
      <h1>Hello World</h1>
    );
  }
}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

index.html

<html>
  <body>
    <div id='root'></div>
    <script src="/static/bundle.js"></script>
  </body>
</html>

Demo16: React router (source)

This demo uses webpack to build React-router's official example.

Let's imagine a little app with a dashboard, inbox, and calendar.

+---------------------------------------------------------+
| +---------+ +-------+ +--------+                        |
| |Dashboard| | Inbox | |Calendar|      Logged in as Jane |
| +---------+ +-------+ +--------+                        |
+---------------------------------------------------------+
|                                                         |
|                        Dashboard                        |
|                                                         |
|                                                         |
|   +---------------------+    +----------------------+   |
|   |                     |    |                      |   |
|   | +              +    |    +--------->            |   |
|   | |              |    |    |                      |   |
|   | |   +          |    |    +------------->        |   |
|   | |   |    +     |    |    |                      |   |
|   | |   |    |     |    |    |                      |   |
|   +-+---+----+-----+----+    +----------------------+   |
|                                                         |
+---------------------------------------------------------+
$ webpack-dev-server --history-api-fallback

参考资料

入门Webpack Webpack:教程