最近公司弄了个有150+页面的项目,心想,终于有机会可以去学习webpack了,以前想学却没有实际项目去引导逼迫去学,现在终于领略到了webpack的强大了。
什么是webpack
在前端的项目开发中,总有大量的页面和样式需要处理,而维护这些文件也成了头疼的问题。
为了简化开发,于是就有很多好的开发方式,如:
模块化开发。每个功能模块都分开成一个个独立的组件,需要的时候再引入。
scss等预处理器。
使用pug, jade 更快编写HTML。
...
这些方式确实可以大大提高开发效率,但是每种方式都有自己的打包方式,还有兼容性处理,如果纯手动处理,必然会增加工作量。
但是,现在有了webpack,上述问题基本解决了,一个webpack就可以处理各种繁琐的过程,给你一个清爽,快速的开发环境。
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。
简单的来说,在项目开发中,
你可以使用js,es6/7,甚至ts开发。
es6/es7, .ts => .js
使用stylus, scss, less 等预处理器编写css
.scss/.less => .css
webpack可以将你项目中的所有文件,处理成浏览器能识别的文件。
开始使用webpack
先新建一个练手用的空文件夹 $ mkdir webpack-demo
,并进入该文件夹。
-
新建
package.json
.$ npm init
-
安装webpack及其基本插件。
$ npm i -D webpack extract-text-webpack-plugin html-webpack-plugin css-loader file-loader style-loader url-loader
其中:
extract-text-webpack-plugin : 该插件主要为了抽离css样式,可以将css从打包的js中抽离出来。以link方式引入样式。
html-webpack-plugin : 该插件主要是用于生成html文件,并可以根据入口文件来引入相应的文件。
css-loader : 解析css文件中的
import
和require
,并处理他们。style-loader : 将css样式通过 style 标签注入到html文件中。
file-loader : 指明webpack将所引入的对象,并返回一个公网能访问的url地址。
url-loader : 将文件转换成 base64编码。
-
配置webpack。
新建一个配置文件:
$ touch webpack.config.js
var webpack = require('webpack'); var path = require('path'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); var HtmlWebpackPlugin = require('html-webpack-plugin'); var webpackConfig = { // 设置入口文件。 entry: './src/js/index.js', output: { // 设置输出文件夹 path: path.join(__dirname, 'dist'), // 设置公用文件夹路径 publicPath: '/', // 设置输出的js文件的名字规则。 // [name] 为chunk中的名称 // [hash] 为webpack生成的哈希值 filename: "js/[name].[hash].bundle.js" }, module: { rules: [{ // 处理css文件 test: /\.css$/, loader: "style-loader!css-loader" }, { // 处理html文件,并处理img 中 src 和 data-src 的引入路径 test: /\.html$/, loader: "html-loader?attrs=img:src img:data-src" }, { // 处理字体文件 test: /\.(woff|woff2|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file-loader?name=./fonts/[name].[ext]' }, { // 处理图片,并将8k以下的图片转为base64编码 test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192&name=./img/[hash].[ext]' }] }, plugins: [ // 公共js提取 new webpack.optimize.CommonsChunkPlugin({ name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk // minChunks: 3 // 提取至少3个模块共有的部分 }), // 提取公共css样式 new ExtractTextPlugin('./css/[name].css'), // 处理html文件。 new HtmlWebpackPlugin({ filename: './view/index.html', //生成的html存放路径,相对于path template: './src/view/index.html', //html模板路径 inject: 'body', //js插入的位置,true/'head'/'body'/false hash: true, //为静态资源生成hash值 // chunks: ['vendors', allDirs[i] + '/' + matches[1]], //需要引入的chunk,不配置就会引入所有页面的资源 minify: { //压缩HTML文件 removeComments: true, //移除HTML中的注释 collapseWhitespace: false //删除空白符与换行符 } }) ], // 设置开发服务器 devServer: { contentBase: path.join(__dirname, "dist/"), host: 'localhost', port: 9090, inline: true } } module.exports = webpackConfig
-
测试配置文件:
先设置开发文件夹目录:- webpack-demo + node_modules - src + js // 存放js文件 + css // 存放css样式 + view // 存放模板文件 webpack.config.js package.json
新建一个 html 文件:
$ touch ./src/view/index.html
。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Hello World</title> </head> <body> <h2>Hello World</h2> <p>This is from webpack-demo</p> </body> </html>
新建css文件:
$ touch ./src/css/index.css
。h2{ color: red; opacity: 0.5; transform: rotateZ(-10deg); } p{ color: green; }
新建js文件:
$ touch ./src/js/index.js
。// 引入css文件。 require('../css/index.css') console.log('hello world');
测试打包:
$ webpack
OK!基本的配置就完成了。
我们可以看到,在项目中多了一个dist文件夹,里面存放的就是刚刚打包好的文件。打开index.html(需要在服务器中打开,并且服务器根目录为dist)可以看到,生成的html文件,征程显示我们写的内容,css样式则直接写入了style标签当中,而且自动引入了两个js文件,其中,vendors是带有公共部分的js文件,index则是我们一开始写的js逻辑文件。
处理scss文件。
既然都用到了webpack自动打包了,那也顺便使用webpack去处理scss文件(个人爱好是scss,less的处理同理),顺便也处理完css中的其他兼容性问题和浏览器前缀问题吧(程序员的思维就是,懒)。
现在css中需要处理的有:
浏览器前缀和大部分兼容性问题: autoprefixer
flex 的兼容性问题: postcss-flexibility
opacity 兼容IE: postcss-opacity
颜色兼容性问题: postcss-color-rgba-fallback
scss文件处理: sass-loader 同时需要依赖
node-sass
压缩css文件: cssnano
安装上书postcss-loader的插件: $ npm i -D autoprefixer postcss-flexibility postcss-opacity postcss-color-rgba-fallback sass-loader node-sass
现在在webpack中处理css的问题,基本都是通过一个 postcss-loader
去完成所有的处理问题。
先新建一个文件夹,用于存放所有的scss文件: $ mkdir ./src/scss
再新建一个index.scss, $ touch ./src/scss/index.scss
body{
background: black;
color: white;
h2{
transform: translateX(10px) rotateZ(-10deg);
color: red;
opacity: 0.5;
}
}
将postcss的加载器中需要的单独提取出来放在一个配置文件中: $ touch postcss.config.js
module.exports = {
plugins: [
// minify css
require('cssnano')({
preset: 'default'
}),
// 处理css前缀
require('autoprefixer')({
browserslist: [
"> 1%",
"last 2 versions",
"Edge",
"ie >= 9"
]
}),
// 处理flex浏览器兼容性
require('postcss-flexibility'),
// 处理css中rgba颜色代码
require('postcss-color-rgba-fallback'),
// 处理css中opacity的IE兼容性。
require('postcss-opacity')
]
}
修改 webpack.config.js:
var webpackConfig = {
...
module: {
rules: [{
// 处理css文件
test: /\.(scss|sass|css)$/,
// loader: "css-loader?importLoaders=1!postcss-loader!sass-loader",
// loader执行顺序是从右到左:sass-loader -> postcss-loader -> css-loader
use: [
"style-loader",
{
loader: "css-loader",
options: {
// // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
importLoaders: 2
}
},
'postcss-loader',
'sass-loader'
]
},
...
]
}
...
}
module.exports = webpackConfig
修改一下入口文件中的样式引入: $ vim ./src/js/index.js
require('../scss/index.scss')
console.log('hello world');
打包测试:
完美通过。
处理pug/jade文件
项目有很多的页面,而且页面之间也有很多相同的页面,之前写惯了vue的组件,所以注册我们也引入了pug作为前端模板引擎。 pug 的前身就是 jade ,所以语法什么都得基本都是一致的。具体的看 官网 。
和处理css的一样,先要安装加载器。 $ npm i -D pug pug-loader
新建一个简单的页面: $ touch ./src/view/index.pug
html
title Test.html
body
h2 Welcome to pug.
p This is from index.pug
修改一下配置文件中的html模板入口:
var webpackConfig = {
...
plugins: [
// 公共js提取
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
}),
// 提取公共css样式
new ExtractTextPlugin('./css/[name].css'),
// 处理html文件。
new HtmlWebpackPlugin({
filename: './view/index.html', //生成的html存放路径,相对于path
template: './src/view/index.pug', //html模板路径
inject: 'body', //js插入的位置,true/'head'/'body'/false
hash: true, //为静态资源生成hash值
chunks: ['vendors', 'index'], //需要引入的chunk,不配置就会引入所有页面的资源
minify: { //压缩HTML文件
removeComments: true, //移除HTML中的注释
collapseWhitespace: false //删除空白符与换行符
}
})
],
...
}
module.exports = webpackConfig
重新编译打包:
使用 babel 来“编译”你的js
在项目开发中,难免会遇到使用 es6 ,甚至 es7 去编写js。但是大部分浏览器却不支持这些语法,这时候,就需要 babel-loader 来处理js,并将其转换为浏览器能识别的 es5 语法。
还是要安装依赖: $ npm i -D babel-loader babel-core babel-preset-es2015
如果用上了es7 的语法,就要根据不同阶段语法提案的转码规则(共有4个阶段),选装一个:
$ npm i -D babel-preset-stage-0
$ npm i -D babel-preset-stage-1
$ npm i -D babel-preset-stage-2
$ npm i -D babel-preset-stage-3
新建一个 .babelrc
babel配置文件: $ touch .babelrc
{
"presets": ["es2015"],
"plugins": []
}
修改 webpack 配置文件:
var webpackConfig = {
...
module: {
rules: [{
test: /\.js$/,
// 不编译 node_modules 下的文件
exclude: /node_modules/,
loader: "babel-loader"
}]
}
...
}
module.exports = webpackConfig
在 index.js 中写个 es6 的语法:
require('../scss/index.scss')
console.log('hello world');
((message) => {
console.log(message);
})('message from es6')
let step = 2;
var add = number => number*=step
console.log(add(2));
来,打包看看:
完美。
配置多文件入口
项目有多个页面,每个页面都对应着一个js入口和一个页面,但是,入口 entry 和 html-webpack-plugin 每次只能配置一个具体的入口文件,如果每增加一个页面就要去配置,那么这无形之中增加了工作量,因此,我们需要一个通用的入口文件。
既然在node中可以访问文件夹并读取其中的文件名,那么我们可以使用node来去循环读取文件夹下的js文件,并自动添加至入口配置中:
新建一个config 文件夹,以存放webpack可变的配置,方便以后修改: $ mkdir config
因为遍历入口文件都是一样的操作流程,所以先写一个工具包:$ touch config/utils.js
var fs = require('fs');
// 递归遍历文件夹,获取入口文件
function getAllFiles(dirRoot, type){
var filterReg = new RegExp('.'+type+'$');
function getAllFileFromDir(root) {
var res = [], files = fs.readdirSync(root)
files.forEach((file) => {
var pathname = root+'/'+file,
state = fs.lstatSync(pathname)
if (!state.isDirectory()) {
// 过滤相对应的文件
filterReg.test(pathname) && res.push(pathname)
// res.push(pathname.replace(dir_root+'/', ''))
}else{
res = res.concat(getAllFileFromDir(pathname))
}
})
return res
}
return getAllFileFromDir(dirRoot)
}
function getEntry(files, replaces){
var entry = {}
for (var i = 0; i < files.length; i++) {
var filename = files[i]
replaces.map((replace) => {
filename = filename.replace(replace, '')
})
entry[filename] = files[i]
}
return entry
}
module.exports = {
getAllFiles,
getEntry
}
再新建一个 js 入口文件配置: $ touch config/webpack.entry.js
var path = require('path');
var utils = require('./utils.js')
var dir_root = path.resolve(__dirname, '../src/js');
// console.log(getAllFiles(dir_root));
var allFiles = utils.getAllFiles(dir_root, 'js')
var entry = utils.getEntry(allFiles, ['js', dir_root+'/'])
console.log(entry);
module.exports = entry
同样的,页面入口文件也添加一个: $ touch config/webpack.plugins.js
var webpack = require('webpack')
var path = require('path');
var utils = require('./utils.js')
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var HtmlWebpackPlugin = require('html-webpack-plugin');
var entry = require('./webpack.entry.js')
var plugins = [
// 公共js提取
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
minChunks: 3 // 提取至少3个模块共有的部分
}),
// 提取公共css样式
new ExtractTextPlugin('./css/[name].css'),
]
let dir_root = path.resolve(__dirname, '../src/view');
var pugFiles = utils.getAllFiles(dir_root, 'pug')
pugFiles = utils.getEntry(pugFiles, ['.pug', dir_root+'/'])
for (var key in pugFiles) {
if (pugFiles.hasOwnProperty(key)) {
let opt = {
filename: './view/'+ key +'.html',
template: pugFiles[key],
hash: true,
minify: { //压缩HTML文件
removeComments: true, //移除HTML中的注释
collapseWhitespace: false //删除空白符与换行符
}
}
if (entry.hasOwnProperty(key)) {
opt['chunks'] = ['vendors', key]
opt['inject']= 'body'
}
console.log(opt);
plugins.push(new HtmlWebpackPlugin(opt))
}
}
module.exports = plugins
再次修改webpack的配置:
var entry = require('./config/webpack.entry.js')
var plugins = require('./config/webpack.plugins.js')
var webpackConfig = {
entry,
plugins,
...
}
module.exports = webpackConfig
可以看到,dist文件夹下,多了几个html文件和js文件。
其他处理
-
提取css到外部link,而非style标签
这个解决办法比较简单,只需要用extract-text-webpack-plugin
插件将css提取出来即可:{ test: /\.(scss|sass|css)$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: [{ loader: "css-loader", options: { // // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader importLoaders: 2 } }, 'postcss-loader', 'sass-loader' ] }) }
再次打包的结果:
列出dis文件夹的目录树可以发现,多了一个css的文件夹。打开页面可以发现,页面中的style标签已经被link替代了。
-
每次打包旧的文件依旧存在
每次执行webpack打包(当然在实际开发中应该使用热更新服务器,不需要频繁打包)都会产生一堆新的js文件。解决办法:
一是将打包的js文件的hash值去掉,这样就会新的打包js文件,将旧的覆盖。
二是使用 clean-webpack-plugin
var CleanWebpackPlugin = require('clean-webpack-plugin'); var plugins = [ // 每次打包前都清空dist文件 new CleanWebpackPlugin(['dist'], { root: path.resolve(__dirname, '../') }), // 公共js提取 new webpack.optimize.CommonsChunkPlugin({ name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk // minChunks: 3 // 提取至少3个模块共有的部分 }), // 提取公共css样式 new ExtractTextPlugin('./css/[name].css'), ]
-
批量新建文件
因为每个页面基本都有3个文件:page.pug
,page.js
,page.scss
,如果每次新加一个页面都要手动新建3个文件的话,效率太低下,能用命令行解决的,当然要用命令行解决啦。先写个小脚本
newpage.sh
:#!/bin/sh if [[ $1 ]]; then filename=$1 touch ./src/js/$filename.js touch ./src/view/$filename.pug touch ./src/scss/$filename.scss fi
使用方式就是:
$ ./newpage.sh filename
,要运行该脚本,就需要先将脚本设置为可执行:$ sudo chmod +x ./newpage.sh
。还可以将它写进 npm 的
package.json
的命令中,则命令可以变为:npm run new -- filename
{ "script": { "new": "./newpage.sh" } }
-
定义路径常量
在编写js时,因为有可能js文件是在很多级的目录当中,如果每次都使用..
来定位上一层目录的话,那么这个定位就会十分繁琐。这时可以使用 webpack 提供的 resolve.alias 配置来使引入文件的时候变得更加方便简单。resolve: { alias: { scss: path.resolve(__dirname, 'src/scss'), js: path.resolve(__dirname, 'src/js'), view: path.resolve(__dirname, 'src/view'), assets: path.resolve(__dirname, 'src/assets') } }
这样,在
other.js
中,就可以直接使用require('scss/about.scss')
而不需要写繁琐的require('../../scss/about.scss')
。这样就方便多了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。