飘雪大前端让前端飞WEB前端程序开发

一步一步学WebPack 2

2017-06-21  本文已影响96人  wavesnow

一步一步学WebPack2

最近将webpack的使用总结一下,便于下一步的使用。本文通过若干的demo,让你一步一步学会使用webpack。webpack中一切都是模块,所有的一切(JS,CSS,图片,HTML)都可以被视作模块,通过require加载。模块加载器会把所有的模块最终打包生成一个巨大的“bundle.js”文件,并且会一直不停进行加载!所以Webpack通过大量的特性去分割你的代码,生成多个“bundle”片段,并且异步地加载项目的不同部分。

npm install http-server -g

Demo1 - Start

{
  "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"
  }
}
#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>

#touch index.js
var app=document.getElementById('app'); 
app.innerHTML="Hello My WebPack App";
npm install webpack –-save-dev
./node_modules/webpack/bin/webpack.js index.js 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 -p 8000

然后在浏览器打开http://localhost:8000

Demo2 - Data Update

npm install rimraf –-save-dev
{
  "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"
  }
}

引入Module,文件监控。

module.exports={ data:'Hello,World!', event:'APP Event'};
var messages=require('./message');

var app=document.getElementById('app'); 
app.innerHTML = messages.data+"From "+ messages.event;
"build":"rimraf dist && webpack src/index.js dist/bundle.js --watch"

Demo3 - Webpack-dev-server 1

npm install webpack-dev-server –-save-dev
var path = require('path'); // 导入路径包 

module.exports={ 
    entry:'./src/index.js',//入口文件 
    output:{ 
        path:path.join(__dirname,'dist'),// 指定打包之后的文件夹 
        publicPath:'/dist/',// 指定资源文件引用的目录 
        filename:'bundle.js'// 指定打包为一个文件 bundle.js 
    }
}
... 
"scripts": { 
"test": "echo \"Error: no test specified\" && exit 1", 
"build": "rimraf dist && webpack --watch", 
"dev": "webpack-dev-server" 
}, 
...

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方式

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(){});
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 
    } 
}
if(module.hot){//启用热重载 
    module.hot.accept();
}
... 
"scripts": { 
"build": "rimraf dist && webpack --watch", 
"dev": "node dev-server.js" 
}, 
...

Demo5 - Development and Production

在入口处区分生产环境和开发环境。

...
"scripts": { 
"build": "rimraf dist && set NODE_ENV=production&& webpack", 
"dev": "set NODE_ENV=development&& node dev-server.js" 
},
...
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

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"]}
...

module: { 
    rules:[ 
        { 
            test: /\.js$/, 
            use: ["babel-loader"], 
            exclude: path.resolve(__dirname, 'node_modules'),
        } 
    ] 
}
    
...
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();
}

Demo7 - File Loader for Image

const icon=require('./img/s1.png'); 
const Image=`![](${icon})`; 
export default Image;
const img=require('./img/s2.jpg'); 
const Image=`![](${img})`; 
export default Image;
npm install file-loader –-save-dev
... 
module: { 
    rules:[ 
        { 
            test: /\.js$/, 
            use: ["babel-loader"], 
            exclude: path.resolve(__dirname, 'node_modules'),
        },
        {
            test: /\.(jpe?g|png)$/,
            use: ['file-loader']
        },
    ] 
},
...
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请求。

npm install url-loader –-save-dev
...
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

const Button={ 
button:'<button id="myButton">Press me</button>', 
attachEl:()=>{ 
document.getElementById('myButton').addEventListener('click',()=>{ 
// debugger; 
console.log('clicked'); 
}) 
}}; 
export default Button;
... 
module.exports={ 
    entry:entry,//入口文件
    devtool:'source-map',//打包代码的同时生成一个sourcemap文件,并在打包文件的末尾添加souceURL注释,注释会告诉JS引擎原始文件位置 
    module: { 
        rules:[ 
            { 
                test: /\.js$/, 
                use: ["babel-loader"], 
                exclude: path.resolve(__dirname, 'node_modules'),
            }
        ] 
    }, 
... 
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

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) 
    }) 
);
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

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

    ] 
},
...
 
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(); 
    } 
}

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

npm install extract-text-webpack-plugin -save-dev
...
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'),
            }

        ] 
    }, 
...
<link rel="stylesheet" href="./dist/style.css">
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>
var HTMLWebpackPlugin=require('html-webpack-plugin');
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这里则不用写
output:{ 
path:path.join(__dirname,'dist'),// 指定打包之后的文件夹 
publicPath: PRODUCTION ? '/' : '/dist/', 
filename: PRODUCTION ? 'bundle.[hash:12].min.js' : 'bundle.js'// 指定打包为一个文件 bundle.js 
}
...
"scripts": {
    "build": "rimraf dist && NODE_ENV=production webpack && http-server ./dist -p 3000 ",
    "dev": "NODE_ENV=development node dev-server.js"
  },

...

demo14 - Tabs Pages

const page=`<h1>页面1</h1>`;
export default page;

const page=`<h1>页面2</h1>`;
export default page;
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();    
}}

Demo15 - Jquery

npm install jquery –-save-dev
import $ from 'jquery'; 
$('#app').css('background','#ff0');
... 
module.exports={ 
    externals:{ 
        'jquery':'jQuery' 
    }, 
devtool:'source-map', 
entry:entry,//入口文件 
plugins:plugins, 
... 
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
上一篇下一篇

猜你喜欢

热点阅读