一步一步学WebPack2
最近将webpack的使用总结一下,便于下一步的使用。本文通过若干的demo,让你一步一步学会使用webpack。webpack中一切都是模块,所有的一切(JS,CSS,图片,HTML)都可以被视作模块,通过require加载。模块加载器会把所有的模块最终打包生成一个巨大的“bundle.js”文件,并且会一直不停进行加载!所以Webpack通过大量的特性去分割你的代码,生成多个“bundle”片段,并且异步地加载项目的不同部分。
- 先安装Node环境。
- 安装安装一个简单的便于测试的server,推荐简单的http-server。
- 源码地址:https://github.com/fujusong/webpack-starter.git
npm install http-server -g
Demo1 - Start
- 建立node项目:npm init ,名字为demo1,得到package.json文件
{
"name": "demo1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/fujusong/webpack-starter.git"
},
"keywords": [
"webpack",
"starter"
],
"author": "sofu",
"license": "MIT",
"bugs": {
"url": "https://github.com/fujusong/webpack-starter/issues"
},
"homepage": "https://github.com/fujusong/webpack-starter#readme",
"devDependencies": {
"webpack": "^2.6.1"
}
}
- 创建index.html ,并新建一个id为app的div。
#touch index.html
#vi index.html
<!DOCTYPE html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
- 创建index.js
#touch index.js
var app=document.getElementById('app');
app.innerHTML="Hello My WebPack App";
- 安装webpack开发依赖
npm install webpack –-save-dev
- 利用webpack将index.js文件打包为bundle.js文件
./node_modules/webpack/bin/webpack.js index.js bundle.js
- 修改index.html文件,加入bundle.js。
<!DOCTYPE html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></script>
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
- 运行http-server,打开浏览器查看页面
http-server -p 8000
然后在浏览器打开http://localhost:8000
Demo2 - Data Update
- 创建src和dist两个文件夹,把index.js移动到src文件夹里,bundle.js是webpack打包生成出来的,使用先删除再生成的办法确保文件正常更新,以后会把dist作为生成文件存放的目标文件夹。
- 安装rimraf 用于打包前删除旧的生成文件。
npm install rimraf –-save-dev
- 修改package.json的script启动方式
{
"name": "demo1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build":"rimraf dist && webpack src/index.js dist/bundle.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/fujusong/webpack-starter.git"
},
"keywords": [
"webpack",
"starter"
],
"author": "sofu",
"license": "MIT",
"bugs": {
"url": "https://github.com/fujusong/webpack-starter/issues"
},
"homepage": "https://github.com/fujusong/webpack-starter#readme",
"devDependencies": {
"rimraf": "^2.6.1",
"webpack": "^2.6.1"
}
}
- 运行 npm run build 打包一下,在dist目录下得到了bundle.js
- 把index.html里面引用bundle.js的路径改成./dist/bundle.js,再运行http-server。
引入Module,文件监控。
- 接下来在src目录下新建messages.js文件
module.exports={ data:'Hello,World!', event:'APP Event'};
- 在index.js文件引入messages.js,并实例到页面
var messages=require('./message');
var app=document.getElementById('app');
app.innerHTML = messages.data+"From "+ messages.event;
- 把package.json修改一下,build的结尾加上监视命令–watch
"build":"rimraf dist && webpack src/index.js dist/bundle.js --watch"
- 运行npm run build重新打包,可以看到打包过程并没有结束跳出,仍然是待命状态。
- 另起一个cli,启动http-server -p 8000,在浏览器输入localhost:8000
- 修改messages.json消息内容,然后刷新浏览器,可以看到内容随即更新了。
Demo3 - Webpack-dev-server 1
- 安装webpack-dev-server
npm install webpack-dev-server –-save-dev
- 创建
webpack.config.js
文件,并添加基本配置
var path = require('path'); // 导入路径包
module.exports={
entry:'./src/index.js',//入口文件
output:{
path:path.join(__dirname,'dist'),// 指定打包之后的文件夹
publicPath:'/dist/',// 指定资源文件引用的目录
filename:'bundle.js'// 指定打包为一个文件 bundle.js
}
}
- 打包所需的路径都已在
webpack.config.js
配置后,package.json
的build则去掉路径,并添加webpack-dev-server
启动:"dev":"webpack-dev-server"
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rimraf dist && webpack --watch",
"dev": "webpack-dev-server"
},
...
- 运行npm run dev启动webpack-dev-server进行打包,在浏览器输入localhost:8080直接打开。
- 修改messages.json的内容,保存,可以看到浏览器实时更新了数据
Demo4 - Webpack-dev-server 2
对于webpack-dev-server,每次修改代码后,webpack可以自动重新打包,webpack-dev-server有两个用于自动刷新模式:iframe和inline,浏览器可以响应代码变化并自动刷新(hot)。
iframe
页面被嵌套在一个iframe下,代码发生改动后,iframe会重新加载
使用此模式无需额外配置,只需访问http://localhost:8080/webpack-dev-server/index.html
即可,显然webpack-dev-server默认的模式就是iframe。
inline
为整个页面提供了“Live reloading”功能。webpack官方提供的一个小型Express服务器。
hot
提供了“模块热重载”功能,它会尝试仅仅更新组件被改变的部分(而不是整个页面)。会尝试先去通过 HMR 更新然后可能尝试刷新整个页面。只需要加上一个 webpack/hot/dev-server entry point
,并且在 dev-server 调用时加上参数 –hot
。如果我们把inline和hot这两个选项都写上,那么当文件被改动时,webpack-dev-server会先尝试HMR,如果这不管用,它就会重新加载整个页面。
hot(HMR)和inline使用有两种方式:CLI和Node.js API
CLI方式比较简单,只需修改package.json中scripts配置,添加 –inline –hot
"dev": "webpack-dev-server –inline –hot"
node.js API方式
- 在主目录下,新建dev-server.js文件
var WebpackDevServer=require('webpack-dev-server');
var webpack=require('webpack');
var config=require('./webpack.config');
var path=require('path');
var compiler=webpack(config);
var server=new WebpackDevServer(
compiler,
{//创建服务器实例
hot:true,//HMR配置
filename:config.output.filename,
publicPath:config.output.publicPath,//必填
stats:{
colors:true
}
}
);
server.listen(8080,'localhost',function(){});
- 修改webpack.config.js配置
var path=require('path');// 导入路径包
var webpack=require('webpack');
module.exports={
entry:[//入口文件
'./src/index.js',
'webpack/hot/dev-server',//调用热重载 hot
'webpack-dev-server/client?http://localhost:8080'
//添加webpack-dev-server客户端
],
plugins:[
new webpack.HotModuleReplacementPlugin()//全局开启热代码替换
],
output:{
path:path.join(__dirname,'dist'),// 指定打包之后的文件夹
publicPath:'/dist/',// 指定资源文件引用的目录
filename:'bundle.js'// 指定打包为一个文件 bundle.js
}
}
- 在index.js最底下添加hot调用
if(module.hot){//启用热重载
module.hot.accept();
}
- 修改package.json启动方式
...
"scripts": {
"build": "rimraf dist && webpack --watch",
"dev": "node dev-server.js"
},
...
- 运行npm run dev ,在浏览器输入
localhost:8080
查看,然后尝试修改index.js
或messages.json
的数据,保存,浏览器自动刷新了
Demo5 - Development and Production
在入口处区分生产环境和开发环境。
- 修改package.json的script项,通过set NODE_ENV来设置环境变量
...
"scripts": {
"build": "rimraf dist && set NODE_ENV=production&& webpack",
"dev": "set NODE_ENV=development&& node dev-server.js"
},
...
- 修改
webpack.config.js
,在入口处添加NODE_ENV
环境判断使用生产环境还是开发环境的入口文件及插件。process.env.NODE_ENV
可以获取到启动文件的环境变量,nodejs的app.get('env')
也可以取得。
var path=require('path');
var webpack=require('webpack');
var DEVELOPMENT=process.env.NODE_ENV==='development';
var PRODUCTION=process.env.NODE_ENV==='production';
var entry=PRODUCTION ? ['./src/index.js'] :
[
'./src/index.js',
'webpack/hot/dev-server',//开启热重载 hot
'webpack-dev-server/client?http://localhost:8080'
//添加webpack-dev-server客户端
];
var plugins=PRODUCTION ? [] :
[
new webpack.HotModuleReplacementPlugin()
//全局开启代码热替换 如果是CLI这里则不用写
];
module.exports={
entry:entry,//入口文件
plugins:plugins,
output:{
path:path.join(__dirname,'dist'),// 指定打包之后的文件夹
publicPath:'/dist/',// 指定资源文件引用的目录
filename:'bundle.js'// 指定打包为一个文件 bundle.js
}
}
3.然后分别运行npm run dev 和npm run build。
Demo6 - babel for es6/es7
- 安装babel核心模块
npm install babel-core babel-loader babel-preset-es2015 babel-preset-stage-0 -save-dev
具体各模块的作用:
"babel-core" //转换器
"babel-loader" //转换器的加载器
"babel-preset-es2015" //ES2015转码规则
"babel-preset-stage-0" //es7支持,ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个stage-0,stage-1,stage-2,stage-3
- 在项目文件添加.babelrc文件,加入转码规则
```json
{ "presets":["es2015","stage-0"]}
- 修改webpack.config.js文件,添加module rules模块设置
...
module: {
rules:[
{
test: /\.js$/,
use: ["babel-loader"],
exclude: path.resolve(__dirname, 'node_modules'),
}
]
}
...
- 在index.js中添加es6格式的方法
var messages=require('./message');
var newMessage = ()=>('<p>'+messages.data +' -From '+ messages.event+"</p>");
var app=document.getElementById('app');
app.innerHTML=newMessage();
if(module.hot){//启用热重载
module.hot.accept();
}
- 运行npm run dev测试,可修改数据验证程序。
Demo7 - File Loader for Image
在
webpack.config.js
的添加devtool:'source-map'
,用于在打包代码的同时生成一个sourcemap文件,并在打包文件的末尾添加//# souceURL,注释会告诉JS引擎原始文件位置在src目录内新建img文件夹,复制二张图片进来供测试用。这里使用的一张名为s1.png的小图片,大小为9k。另一张名为s2.jpg,大小191k。
在src目录内新建Icon.js文件
const icon=require('./img/s1.png');
const Image=`![](${icon})`;
export default Image;
- 在src目录内新建Img.js文件
const img=require('./img/s2.jpg');
const Image=`![](${img})`;
export default Image;
- 安装文件加载器
npm install file-loader –-save-dev
- 修改webpack.config.js文件,添加file-loader加载器
...
module: {
rules:[
{
test: /\.js$/,
use: ["babel-loader"],
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.(jpe?g|png)$/,
use: ['file-loader']
},
]
},
...
- 修改index.js,通过import加载图片到页面
import icon from './Icon';
import img from './Img';
var messages=require('./message');
var newMessage = ()=>('<p>'+messages.data +' -From '+ messages.event+"</p>");
var newMessage2=()=>(
`<p>${icon} ${img}</p>`
);
var app=document.getElementById('app');
app.innerHTML=newMessage();
app.innerHTML+=newMessage2();
if(module.hot){//启用热重载
module.hot.accept();
}
Demo8 - URL Loader for Images
使用URL Loader加载图片,limit参数可以使用base64将小图片内联在代码中,减少http请求。
- 安装url-loader
npm install url-loader –-save-dev
- 修改
webpack.config.js
,将图片的loaders改为url-loader?limit=10000&name=images/[hash:12].[ext]
,小于10k的图片将会以base64的形式内联在代码中,并且图片打包到images文件内,以哈希值命名。
...
module: {
rules:[
{
test: /\.js$/,
use: ["babel-loader"],
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.(jpe?g|png)$/,
use: ['url-loader?limit=10000&name=images/[hash:12].[ext]']
},
]
},
...
3.输入npm run dev
测试,可以看到s1.png已经被base64内联。s2.jpg文件名称也变成了hash名称。
Demo9 - Add Button
- 在src文件目录新建button.js
const Button={
button:'<button id="myButton">Press me</button>',
attachEl:()=>{
document.getElementById('myButton').addEventListener('click',()=>{
// debugger;
console.log('clicked');
})
}};
export default Button;
- 修改webpack.config.js,添加dev-tool:’sourse-map’,打包代码的同时生成一个sourcemap文件。
...
module.exports={
entry:entry,//入口文件
devtool:'source-map',//打包代码的同时生成一个sourcemap文件,并在打包文件的末尾添加souceURL注释,注释会告诉JS引擎原始文件位置
module: {
rules:[
{
test: /\.js$/,
use: ["babel-loader"],
exclude: path.resolve(__dirname, 'node_modules'),
}
]
},
...
- 修改index.js文件,加载button模块并实例化。
import Button from './button';
var newMessage=()=>(Button.button);
var app=document.getElementById('app');
app.innerHTML=newMessage();
Button.attachEl();
if(module.hot){
module.hot.accept();
}
- 运行测试,点击页面中的按钮,可以看到控制台输出了测试文字
Demo10 - compress source code
- 修改webpack.config.js里的plugins变量,添加生产模式时加载插件。
var plugins = PRODUCTION ? [
new webpack.optimize.UglifyJsPlugin(/*{//代码压缩
comments:true,//显示注释
mangle:false,//取消代码混淆
compress:{
warnings:true//在UglifyJs删除没有用到的代码时不输出警告
}
}*/)
] : [
new webpack.HotModuleReplacementPlugin()//全局开启代码热替换 如果是CLI这里则不用写
];
plugins.push(
new webpack.DefinePlugin({
DEVELOPMENT:JSON.stringify(DEVELOPMENT),
PRODUCTION:JSON.stringify(PRODUCTION)
})
);
- 修改index.js,将环境信息输出到页面
var newMessage = ()=>{
return `DEV:${DEVELOPMENT.toString()} <br> PRO:${PRODUCTION.toString()}`;
}
var app=document.getElementById('app');
app.innerHTML=newMessage();
if(DEVELOPMENT){
if(module.hot){//启用热重载
module.hot.accept();
}
}
3.分别运行npm run dev 和 npm run build可以看到页面上显示的环境,再查看bundle.js,可以看到代码已经按照设置输出。
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rimraf dist && NODE_ENV=production webpack && http-server -p 3000",
"dev": "NODE_ENV=development node dev-server.js"
},
...
4.回到webpack.config.js,把UglifyJsPlugin的参数都去掉,使用默认参数
var plugins=PRODUCTION ? [
new webpack.optimize.UglifyJsPlugin()
] : [
new webpack.HotModuleReplacementPlugin()//全局开启代码热替换 如果是CLI这里则不用 写
];
5.再次运行npm run build打包,此时bundle.js已经被压缩至最小化。
Demo 11 - Load CSS
- 安装css-loader和style-loader:npm install css-loader style-loader -save-dev。
- css-loader 在js文件里,通过require的方式来引入css
- style-loader 在html中以style的方式嵌入css
2.在src目录下,创建style文件夹,并创建一个css文件,这里命名为:globalStyle.css
body{
background:#ddd;
}
:local(.box){
background-color:#ff0;
padding:1em;
border:1px solid #000;
}
3.在webpack.config.js
添加css的loader,webpack的loader的配置是从右往左的,就是先使用css-loader之后使用style-loader
...
module: {
rules:[
{
test: /\.js$/,
use: ["babel-loader"],
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.css$/,
loaders: ["style-loader","css-loader"],
exclude: path.resolve(__dirname, 'node_modules'),
}
]
},
...
- 修改index.js,引入css文件,并将相应的class添加进页面。
var style=require('./style/globalStyle.css');
const newMessage=()=>(
`<div class="${style.box}">
DEV:${DEVELOPMENT.toString()}<br>
PRO:${PRODUCTION.toString()}<br>
</div>`
var app=document.getElementById(‘app’);
app.innerHTML=newMessage();
if(DEVELOPMENT){
if(module.hot){//启用热重载
module.hot.accept();
}
}
- 测试页面,可以看到页面的样式已经更新,查看源码,原本div的classs为box的,已经被hash名称所代替。hash名称可以在
webpack.config.js
里css的加载器里自定义
...
test: /\.css$/,
use: ["style-loader","css-loader?localIdentName=[path][name]--[local]"],
...
- 当需要将生产环境和开发环境命名进行区分时,可以写在一个变量或常量里
...
const cssIdentifier=PRODUCTION? '[hash:base64:10]' : '[path][name]---[local]';
...
...
use: ["style-loader","css-loader?localIdentName=" + cssIdentifier],
...
...
查看页面源码时,可以看到webpack打包时,是把样式文件以style的方式嵌在head里的
Demo12 - CSS Load Seprate
- 安装插件用于将css样式打包成独立的文件。
npm install extract-text-webpack-plugin -save-dev
- 需求是在开发环境样式还是嵌在页面上不变,只在打包到生产环境时,才单独打包到一个叫
style.css
的文件里,因此webpack.config.js
里,先引入extract-text-webpack-plugin
插件,然后在插件栏设置打包时的名称以及提取css加载时的路径
...
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var DEVELOPMENT=process.env.NODE_ENV==='development';
var PRODUCTION=process.env.NODE_ENV==='production';
var entry=PRODUCTION ? ['./src/index.js'] : [
'./src/index.js',
'webpack/hot/dev-server',//开启热重载 hot
'webpack-dev-server/client?http://localhost:8080'//添加webpack-dev-server客户端
];
var plugins = PRODUCTION ? [
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin('style.css'),
] :
[
new webpack.HotModuleReplacementPlugin()//全局开启代码热替换 如果是CLI这里则不用写
];
plugins.push(
new webpack.DefinePlugin({
DEVELOPMENT:JSON.stringify(DEVELOPMENT),
PRODUCTION:JSON.stringify(PRODUCTION)
})
);
const cssIdentifier=PRODUCTION? '[hash:base64:10]' : '[path][name]--[local]';
const cssLoader=PRODUCTION ? ExtractTextPlugin.extract('css-loader?localIdentName=' + cssIdentifier) :
['style-loader','css-loader?localIdentName=' + cssIdentifier];
module.exports={
entry:entry,//入口文件
module: {
rules:[
{
test: /\.js$/,
use: ["babel-loader"],
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.css$/,
//use: ["style-loader","css-loader"],
use: cssLoader,
exclude: path.resolve(__dirname, 'node_modules'),
}
]
},
...
- index.html页面要单独加载打包生成的样式文件style.css
<link rel="stylesheet" href="./dist/style.css">
然后运行运行npm run build 查看,此时style.css是单独生成的。
style.css的名称也可以用hash表示
new ExtractTextPlugin(
'style-[contenthash:10].css'//根据内容生成hash值
,{allChunks: true}//所有分离文件的样式也会全部压缩到一个文件上
)
Demo13 - HTML Template
- 安装模板插件
npm install html-webpack-plugin -save-dev
2.创建html模版:index-template.html
<!DOCTYPE html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div id="app"></div>
</body>
</html>
- 在
webpack.config.js
顶部导入模版插件
var HTMLWebpackPlugin=require('html-webpack-plugin');
- 在plugins处实例化,并将模版文件设置为
index-template.html
var plugins=PRODUCTION ? [
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin(
'style-[contenthash:10].css'//根据内容生成hash值
),
new HTMLWebpackPlugin({// webpack 指定目录(package内设置)生成静态HTML文件
template:'index-template.html'
})
] : [
new webpack.HotModuleReplacementPlugin()//全局开启代码热替换 如果是CLI这里则不用写
- 静态文件在dist内生成名为index.html的文件,其他资源路径将和开发环境不同,所以需要设置区分判断。
output:{
path:path.join(__dirname,'dist'),// 指定打包之后的文件夹
publicPath: PRODUCTION ? '/' : '/dist/',
filename: PRODUCTION ? 'bundle.[hash:12].min.js' : 'bundle.js'// 指定打包为一个文件 bundle.js
}
- 修改package.json,运行npm run build打包测试。
...
"scripts": {
"build": "rimraf dist && NODE_ENV=production webpack && http-server ./dist -p 3000 ",
"dev": "NODE_ENV=development node dev-server.js"
},
...
demo14 - Tabs Pages
- 在src文件内新建
page1.js
const page=`<h1>页面1</h1>`;
export default page;
- 在src文件内新建
page2.js
const page=`<h1>页面2</h1>`;
export default page;
- 修改index.js,创建两个按钮,并添加相应的点击事件,事件触发时使用System导入页面并将页面的的内容复制到content内。
var app=document.getElementById('app');
app.innerHTML=`
<div id="menu">
<button id="loadPage1">Load1</button>
<button id="loadPage2">Load2</button>
</div>
<div id="content">
<h1>home</h1>
</div>`;
document.getElementById('loadPage1').addEventListener('click',()=>{
//System.import 会令每个可能的模块都产生一个独立的块(chunk)。
System.import('./page1').then(pageModule=>{
document.getElementById('content').innerHTML=pageModule.default;
})
});
document.getElementById('loadPage2').addEventListener('click',()=>{
System.import('./page2').then(pageModule=>{
document.getElementById('content').innerHTML=pageModule.default;
})
});
if(DEVELOPMENT){
if(module.hot){//启用热重载
module.hot.accept();
}}
- 运行npm ren dev测试,在这之前,别忘记把index.html引用css和js的路径改回来
- 打开浏览器的network,来回点击按钮1和按钮2,观察network加载的内容,可以发现,0.bandle.js和1.bandle.js是异步加载,并且不会被重复加载。
Demo15 - Jquery
- 安装jquery插件
npm install jquery –-save-dev
- 在index.js中导入jquery,并创建一个jquery实例
import $ from 'jquery';
$('#app').css('background','#ff0');
- 运行测试时,app背景已经发生改变。运行打包后,jquery也被打包进了bundle.js,这并不是我们想要的。而且,jquery并非全局,仅可在index.js使用,想要将jquery加载为全局模块,需要在webpack.config.js中设置externals
...
module.exports={
externals:{
'jquery':'jQuery'
},
devtool:'source-map',
entry:entry,//入口文件
plugins:plugins,
...
- 然后jquery需要从外部加载进来,分别添加到
index.html
和index-template.html
的head中,建议使用网上的jquery的CDN。如:
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
- 分别运行开发环境和生产环境查看,jquery从外部加载,没有被打包进bundle.js。