写在开头:

此 Webpack 教程是阮老师在 Webpack 1.x 的版本上做的。现在 Webpack 的版本已经改动较大,建议有基础的同学,就直接上官网看最新的就好了。这个教程可以用来了解下 Webpack 的前世今生。

仓库地址: https://github.com/userkang/webpack-demos-cn

开始

这个项目是一些简单的 Webpack 示例集合

这些示例特意用简单明了的方式编写,你将会发现跟着这些例子学习这个强大的工具并非难事。

如何使用

首先,全局安装 Webpack 和 webpack-dev-server

$ npm i -g webpack webpack-dev-server

然后,克隆这个仓库

$ git clone https://github.com/userkang/webpack-demos-cn.git

安装依赖

$ cd webpack-demos
$ npm install

现在,去项目 demo* 目录下开始源码体验之旅吧

$ cd demo01
$ npm run dev

如果上面的命令没有自动打开浏览器,你可能需要自己在浏览器访问
http://127.0.0.1:8080

前言:什么是 Webpack

Webpack 是为浏览器构建 JavaScirpt 模块脚本的前端工具

它可以像 Browserify 一样使用,并且更加强大

$ browserify main.js > bundle.js
# 相当于
$ webpack main.js bundle.js

Webpack 需要一个 webpack.config.js 的配置文件,它只是一个 CommonJs 模块

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

有了这个文件之后,你可以不带参数的调用 Webpack

$ webpack

你需要了解一些命令行选项

  • webpack – 开发环境构建
  • webpack -p – 生产环境构建(压缩混淆脚本)
  • webpack --watch – 监听变动并自动打包
  • webpack -d – 生成 map 映射文件
  • webpack --colors – 构建过程带颜色输出

你可能需要像下面这样在 package.json 中自定义一些脚本 scripts

// package.json
{// ..."scripts": {"dev": "webpack-dev-server --devtool eval --progress --colors","deploy": "NODE_ENV=production webpack -p"},// ...
}

目录

  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. React router

Demo01: Entry file (source)

入口文件是 Webpack 进行读取构建 bundle.js 文件的一个文件

例如, main.js 就是一个入口文件.

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

index.html

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

Webpack 遵循 webpack.config.js 来构建 bundle.js.

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

启动服务,访问 http://127.0.0.1:8080 .

$ cd demo01
$ npm run dev

Demo02: Multiple entry files (source)

多个入口文件也是可以的。在多页面 应用中,每个页面拥有不同的入口文件,用这个就非常管用了。

// 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 是一种预处理器,它可以在 Webpack 编译之前把你应用中的静态资源进行转换 (更多信息)。

举个例子, Babel-loader 可以在 Webpack 编译这些 JS 文件之前,先将 JSX/ES6 语法的文件转换成普通 ES5 语法的文件。Webpack 官网可以查看目前支持的 loaders。

main.jsx 是一个 JSX 文件.

// main.jsx
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: {rules: [{test: /\.jsx?$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['es2015', 'react']}}}]}
}

上边的代码段用到了 babel-loader, 它需要 Babel 的预设插件 babel-preset-es2015 and babel-preset-react 来转义 ES6 和 React。

Demo04: CSS-loader (source)

Webpack 允许在 JS 文件里包含 CSS,并通过 CSS-loader来预处理 CSS 文件。

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: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']}]}
}

注意,你必须用到两个 loaders 去转换 CSS 文件。一个是 CSS-loader 来读取 CSS 文件,另一个是 Style-loader 用来把 <style> 标签插入 HTML 页面。

接下来,让我们启动服务。

$ cd demo04
$ npm run dev

事实上,Webpack 会将一个内联样式表插入到 index.html

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

Demo05: Image loader (source)

Webpack 可以在 JS 文件中包含图片。

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: {rules: [{test: /\.(png|jpg)$/,use: [{loader: 'url-loader',options: {limit: 8192}}]}]}
}

url-loader
将图片文件转成 img 标签。如果图片的大小小于 8192 字节,将会被转成 base64 的格式,否则还事会转成一条普通的链接。

启动服务就可以看到,small.pngbig.png 这两张图片转成了不同的两种格式。

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

Demo06: CSS Module (source)

css-loader?modules 的 CSS Module 功能可以给 JS 模块的 CSS 设置一个局部作用域。你可以用 :global(selector) (更多) 关掉它,使样式变成全局的。

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

/* local scope */
.h1 {color: red;
}/* global scope */
: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: {rules: [{test: /\.js[x]?$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['es2015', 'react']}}},{test: /\.css$/,use: [{loader: 'style-loader'},{loader: 'css-loader',// 开启 CSS Module 功能options: {modules: true}}]}]}
}

启动服务。

$ cd demo06
$ npm run dev

访问 http://127.0.0.1:8080, 你将看到 h1 是红色的,因为它的样式是局部作用域,h2 是蓝色的,因为它的作用域是全局的。

Demo07: UglifyJs Plugin (source)

Webpack 用一套插件系统扩展了它的功能。比如,UglifyJs Plugin 就是其中的一个流行的插件,它可以压缩混淆输出的 js 代码。

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 = require('uglifyjs-webpack-plugin')module.exports = {entry: './main.js',output: {filename: 'bundle.js'},// 使用插件plugins: [new UglifyJsPlugin()]
}

启动服务,将会看到 main.js 被压缩成了下面的样式

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 将会
为你创建一个 index.html 文件。 open-browser-webpack-plugin 可以在 Webpack 编译完成后打开一个新窗口。

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'})]
}

启动服务。

$ cd demo08
$ npm run dev

现在你不仅不需要手动的写 index.html 文件,而且也不用手动的打开浏览器了。Webpack 都帮你完成了。

Demo09: Environment flags (source)

你可以用环境变量来区分开发环境。

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')// DefinePlugin 允许创建一个在编译时可以配置的全局常量
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]
}

现在打开 demo09/package.json,你需要在 scripts 字段下添加如下的代码,向 webpack 传递环境变量。

// package.json
{// ..."scripts": {"dev": "cross-env DEBUG=true webpack-dev-server --open",},// ...
}

启动服务。

$ cd demo09
$ npm run dev

Demo10: Code splitting (source)

对于一个大型 app,把所有代码塞到一个文件可能不是很好维护。Webpack 可以把一个庞大的 JS 文件拆分成几块。尤其,有些代码只有用的时候才引入可以按需加载。

Webpack 用 require.ensure 来定义一个代码点。

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

require.ensure 告诉 Webpack ./a.js 应该从 bundle.js 中拆分出来,单独生成一个文件。

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

现在 Webpack 来负责依赖、输出和运行时需要的一些东西。你不必在投入过多的精力在 index.htmlwebpack.config.js 上了。

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

webpack.config.js

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

启动服务。

$ cd demo10
$ npm run dev

表面上,你不会感到什么变化。然而,Webpack 已经把 main.jsa.js 构建成两个不同的文件(bundle.js and 0.bundle.js),并且按依赖关系依次引入。

Demo11: Code splitting with bundle-loader (source)

另一种进行代码分离的方法是用 bundle-loader。

// main.js// 现在 a.js 被引入,它将会被打包到另一个文件
var load = require('bundle-loader!./a.js')// 为了等待 a.js 中的模块变得可用
// 你可能需要使用 async wait
load(function(file) {document.open()document.write('<h1>' + file + '</h1>')document.close()
})

require('bundle-loader!./a.js') 告诉 Webpack 从另一个包里加载 a.js

现在 Webpack 将会构建 main.jsbundle.js 中, a.js0.bundle.js 中。

Demo12: Common chunk (source)

当多个 Js 文件有共同的依赖,我们可以 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="commons.js"></script><script src="bundle1.js"></script><script src="bundle2.js"></script></body>
</html>

上面的 commons.js 就是 main1.jsxmain2.jsx 的公共部分。也就是包含了 reactreact-dom

webpack.config.js

var webpack = require('webpack')module.exports = {entry: {bundle1: './main1.jsx',bundle2: './main2.jsx'},output: {filename: '[name].js'},module: {rules: [{test: /\.js[x]?$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['es2015', 'react']}}}]},plugins: [new webpack.optimize.CommonsChunkPlugin({name: 'commons',// (the commons chunk name)filename: 'commons.js'// (the filename of the commons chunk)})]
}

Demo13: Vendor chunk (source)

你也可以用 CommonsChunkPlugin 提取第三方的 Js 生成一个单独的文件。

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({name: 'vendor',filename: 'vendor.js'})]
}

上面的代码,entry.vendor: ['jquery'] 告诉 Webpack jeuery 应该被打包到一个公共 vendor.js 包中去。

如果你想让一个全局的变量在每一个模块可用,比如 $jQuery 不用 require("jquery") 就可以直接用。那么你需要使用 ProvidePlugin (Official doc) 这个插件,它可以自动的载入模块,而不需要到处 import 或者 require。

// 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'})]
}

当然,这种情况,你还需要手动的全局的载入 jquery.js

Demo14: Exposing global variables (source)

如果你想要用到一些全局变量,但是又不想在打包的时候,把它打包到 bundle 文件中去。这个时候你可以在 webpack.config.js 中配置 externals 字段。

比如,我们定义了一个 data.js 文件

// data.js
var data = 'Hello World'

index.html

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

注意,这里 Webpack 只会打包 bundle.js, 而不会打包 data.js

我们可以 data 字段暴露盛一个全局变量

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

现在,你可以在脚本中像引入其他模块一样引入 data。但它实际是一个全局变量。

// main.jsx
var data = require('data')
var React = require('react')
var ReactDOM = require('react-dom')ReactDOM.render(<h1>{data}</h1>, document.body)

你同样可以把 reactreact-dom 添加到 externals 中,这样可以大大缩短编译时间和编译后 bundle.js 文件的大小。

Demo15: React router (source)

这个演示用 webpack 来构建 React-router 的官方例子.

让我们想象一个带有仪表盘、收件箱和日历的小应用。

+---------------------------------------------------------+
| +---------+ +-------+ +--------+                        |
| |Dashboard| | Inbox | |Calendar|      Logged in as Jane |
| +---------+ +-------+ +--------+                        |
+---------------------------------------------------------+
|                                                         |
|                        Dashboard                        |
|                                                         |
|                                                         |
|   +---------------------+    +----------------------+   |
|   |                     |    |                      |   |
|   | +              +    |    +--------->            |   |
|   | |              |    |    |                      |   |
|   | |   +          |    |    +------------->        |   |
|   | |   |    +     |    |    |                      |   |
|   | |   |    |     |    |    |                      |   |
|   +-+---+----+-----+----+    +----------------------+   |
|                                                         |
+---------------------------------------------------------+

webpack.config.js

module.exports = {entry: './index.js',output: {filename: 'bundle.js'},module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.jsx?$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['es2015', 'react']}}}]}
}

index.js

import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'import './app.css'class App extends React.Component {render() {return (<div><header><ul><li><Link to="/app">Dashboard</Link></li><li><Link to="/inbox">Inbox</Link></li><li><Link to="/calendar">Calendar</Link></li></ul>Logged in as Jane</header><main><Switch><Route exact path="/" component={Dashboard} /><Route path="/app" component={Dashboard} /><Route path="/inbox" component={Inbox} /><Route path="/calendar" component={Calendar} /><Route path="*" component={Dashboard} /></Switch></main></div>)}
}class Dashboard extends React.Component {render() {return (<div><p>Dashboard</p></div>)}
}class Inbox extends React.Component {render() {return (<div><p>Inbox</p></div>)}
}class Calendar extends React.Component {render() {return (<div><p>Calendar</p></div>)}
}render(<BrowserRouter><Route path="/" component={App} /></BrowserRouter>,document.querySelector('#app')
)

index.html

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

启动服务。

$ cd demo15
$ npm run dev

相关链接

  • Webpack docs
  • webpack-howto, by Pete Hunt
  • SurviveJS Webpack book, by Juho Vepsäläinen
  • Diving into Webpack, by Web Design Weekly
  • Webpack and React is awesome, by Christian Alfoni
  • Browserify vs Webpack, by Cory House

证书

MIT

阮一峰 Webpack 教程相关推荐

  1. react入门(1)之阮一峰react教程

    阮一峰老师的github地址:React Demos React 入门实例教程 1.HTML模板 <!DOCTYPE html> <html><head>// re ...

  2. 拜读阮一峰JavaScript教程笔记

    读了不知道几遍,每次都没总结合做笔记,所以很多内容都记不住,如果平时没应用到实际中,那知识跟像过眼云烟,于是还是谢谢博客记记笔记,好好学习天天向上. 原教程链接: http://javascript. ...

  3. 「前端基础」阮一峰JavaScript教程笔记(一)

    文章目录 入门篇 6 条件语句 数据类型 概述 null, undefined 和布尔值 1. null 和 undefined 2. 布尔值 数值 1 概述 2 数值的表示法 3 数值的进制 4 特 ...

  4. 「前端基础」阮一峰JavaScript教程笔记(二)

    文章目录 语法专题 数据类型的转换 1 概述 2 强制转换 3 自动转换 错误处理机制 1 Error 实例对象 2 原生错误类型 2.1 SyntaxError 对象 2.2 ReferenceEr ...

  5. 阮一峰 react 系列教程

    阮一峰 react 系列教程 阮一峰 react 系列教程 ES6 语法:教程 Babel:教程 React:教程,示例库 Webpack:教程 React 项目脚手架:代码库 Flex 布局:教程, ...

  6. python作者 es6_ES6 全套教程 ECMAScript6 (原著:阮一峰)(1)

    ES6 前言 ES6是JS(JavaScript)的一个版本标准, 前端教程共编系统 收录此教程, 遵从"保持署名-非商用"创意共享4.0许可证, 借本站教程共编系统接受广大工程师 ...

  7. 阮一峰:jQuery官方基础教程笔记

    原文地址:http://www.jobbole.com/entry.php/1151 jQuery是目前使用最广泛的javascript函数库. 据统计,全世界排名前100万的网站,有46%使用jQu ...

  8. 阮一峰 React Router 教程

    阮一峰 React Router 教程 本文介绍 React 体系的一个重要部分:路由库 React-Router.它是官方维护的,事实上也是唯一可选的路由库.它通过管理 URL,实现组件的切换和状态 ...

  9. Javascript 基础教程 阮一峰

    Javascript 基础教程 阮一峰 2021-11-16 阮一峰推荐的入门教程,重在查漏补缺 https://wangdoc.com/javascript/index.html 第一章 入门 JS ...

最新文章

  1. 在Xcode中使用Git进行源码版本控制
  2. JAVA 获取文件的MD5值大小以及常见的工具类
  3. python并发编程之semaphore(信号量)_Python 并发编程系列之多线程
  4. java程序设计图形题_面向对象与Java程序设计基础题目:设计一个程序可以一计算平面图形的面积和立体图形的体积。1.使用interface关键...
  5. java中清空文件夹_java 删除文件夹中的所有内容而不删除文件夹本身的实例
  6. 《水经注全国离线地图5.0》升级至5.1
  7. .NET Framework高低版本兼容问题解决办法
  8. 线性回归相关系数c语言,线性回归方程和线性相关系数计算实例
  9. 16进制字符串转16进制整数
  10. java程序员修炼教学视频,快来看鸭~
  11. 量子计算黑客松大赛-量子计算编程
  12. 如何将ppt改为无法修改的pdf
  13. SWF文件格式详解(1)
  14. java分流什么意思_Flink如何分流数据
  15. 磁盘被写保护怎么办?5个方案解除它
  16. 3款开源软件帮你缩短链接
  17. 安装方式B--使用ClouderaManager的Parcels包进行安装
  18. 《FLUENT 14.0超级学习手册》——1.4 常用的商业CFD软件
  19. Java——闰年的判断方法,闰年概念
  20. 基于vue2.0+ 抽奖项目

热门文章

  1. python已安装这个产品的另一个版本_电脑安装会声会影过程中提示已安装这个产品的另一个版本怎么解决...
  2. 好不容易从流水线转码农的我又“失业”了...
  3. 工信部副部长罗文:中国物联网发展的四大特点和几点建议
  4. 单片机printf使用
  5. play_video
  6. openwrt下载安装中文语言包(离线版)
  7. 安装Linux CentOS 7.7
  8. C语言学习二:VS的使用
  9. 猴年马月谈GBDP2004高速编程技术体系
  10. mate10android系统通知,老用户的福音!Mate 10用户也能更新EMUI 10了