干货丨正确的Webpack配置姿势,快速启动各式框架!

在去年的这个时候,本骚年还在被Grunt和Gulp以及各种Requirejs、Seajs团团围住攻击,狼狈不堪。后面认识了Webpack之后,基本所有项目框架都拿它来构建了。

当然也不包括本骚年负责项目都是纯前端的PC端单页应用的原因,还没遇到什么项目使用Webpack上太难的问题。

Hello Webpack
Webpack是一个现代的JavaScript应用程序的模块打包器(module bundler)。其实Webpack不应该拿来跟Grunt/Gulp比较的,但在本骚年这边它就是承担起了很大一部分工作。

初始Webpack
这里主要基于Webpack2来讲吧,Webpack1迁移到2还是不是特别难的,官方也配了迁移文档。
其实官方的文档也有很详细的说明了,对于一般的项目还是可以完全驾驭的。

下面我们先跟随着原始的脚步过一遍概念吧。

四个核心概念:入口(entry)、输出(output)、loader、插件(plugins)。

入口(entry)
将您应用程序的入口起点认为是根上下文(contextual root)或app第一个启动文件。
一般来说,在Angular中我们将是启动.bootstrap()的文件,在Vue中则是new Vue()的位置,在React中则是ReactDOM.render()或者是React.render()的启动文件。

module.exports = {

entry: './path/to/my/entry/file.js'

};

同时,entry还可以是个数组,这个时候「文件路径(file path)数组」将创建“多个主入口(multi-main entry)”。

常见的使用方式是我们需要把”babel-polyfill.js”这样的文件也注入进去(如果需要React的话还可以加个”react-hot-loader/patch”进去):


entry: ['babel-polyfill', './path/to/my/entry/file.js']

};

出口(output)
output属性描述了如何处理归拢在一起的代码(bundled code),在哪里打包应用程序。一般需要以下两点:

filename: 编译文件的文件名(main.js/bundle.js/index.js等)

path:对应一个绝对路径,此路径是你希望一次性打包的目录

module.exports = {

output: {

filename: 'bundle.js',

path: '/home/proj/public/assets'

}

};

loader
webpack把每个文件(.css, .html, .scss, .jpg, etc.) 都作为模块处理。但webpack只理解JavaScript。

如果你看过生成的bundle.js代码就会发现,Webpack将所有的模块打包一起,每个模块添加标记id,通过这样一个id去获取所需模块的代码。
而我们的loader的作用,就是把不同的模块和文件转换为这样一个模块,打包进去。

loader支持链式传递。能够对资源使用流水线(pipeline)。loader链式地按照先后顺序进行编译,从后往前,最终需要返回javascript。

不同的应用场景需要不同的loader,这里我简单介绍几个(loader使用前都需要安装,请自行查找依赖安装):

  1. babel-loader

官网在此,想要了解的也可以参考Babel 入门教程。
babel-loader将ES6/ES7语法编译生成ES5,当然有些特性还是需要babel-polyfill支持的(Babel默认只转换新的JavaScript句法,而不转换新的API,如Promise等全局对象)。

// 在webpack1里使用loader属性,在webpack2中为rules属性

module.exports = {

module: {

rules: [

{test: /\.(js)$/, use: 'babel-loader'}

]

}

};

而对于babel-loader的配置,可以通过options进行,但一般更常使用.babelrc文件进行:

{

"presets": [],

"plugins": [] // 插件

}

presets: 设定转码规则

有”es2015”, “stage-0/1/2/3”,如果你使用React则还加上”react”,而我一般使用”lastest”装满最新特性。
当然这些都需要安装,你选择了对应的转码规则也要安装相应的依赖:

npm install --save-dev babel-preset-latest
  1. ts-loader

一看就知道,是个typescript的loader,同样的还有awesome-typescript-loader,关于两者的不同可参考作者的话。
这里我们使用ts-loader也就足够了:

{

test: /\.(ts)$/,

use: ["babel-loader", "ts-loader"],

exclude: /node_modules/

}

其他loader

css相关loader

css-loader: 处理css文件中的url()

style-loader: 将css插入到页面的style标签

less-loader: less转换为css

postcss-loader(autoprefixer-loader): 自动添加兼容前缀(-webkit-、-moz-等)

url-loader/file-loader: 修改文件名,放在输出目录下,并返其对应的url

url-loader在当文件大小小于限制值时,它可以返回一个Data Url

html-loader/raw-loader: 把Html文件输出成字符串

html-loader默认处理html中的<img src="image.png">为require(“./image.png”),需要在配置中指定image文件的加载器

插件(plugins)
loader仅在每个文件的基础上执行转换,插件目的在于解决loader无法实现的其他事。
由于plugin可以携带参数/选项,需要在wepback配置中,向plugins属性传入new实例。

这里也介绍几个常用的插件:

  1. HtmlwebpackPlugin

功能有下:

为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题

可以生成创建html入口文件,比如单页面可以生成一个html文件入口

但其实最常使用的,无非是把index.htnm页面插入(因为入口文件为js文件):

new HtmlwebpackPlugin({

template: path.resolve(__dirname, 'src/index.html'),

inject: 'body'

})
  1. CommonsChunkPlugin

提取代码中的公共模块,然后将公共模块打包到一个独立的文件中,以便在其他的入口和模块中使用。

像之前有个项目有远程服务器调试代码的需求[捂脸],这时候我们需要把依赖单独抽离出来(不然文件太大了):

new CommonsChunkPlugin({

name: 'vendors',

filename: 'vendors.js',

minChunks: function(module) {

return isExternal(module);

}

})

关于isExternal()函数,用了很简单的方式进行:

function isExternal(module) {

var userRequest = module.userRequest;

if (typeof userRequest !== 'string') {

return false;

}

return userRequest.indexOf('node_modules') >= 0; // 是否位于node_modules里

}
  1. webpack.ProvidePlugin

定义标识符,当遇到指定标识符的时候,自动加载模块。像我们常用的jQuery:

new webpack.ProvidePlugin({

jQuery: 'jquery',

$: 'jquery'

})
  1. ExtractTextPlugin
    可以将样式从js中抽出,生成单独的.css样式文件(同样因为方便调试[捂脸+1])。即把所以的css打包合并:
new ExtractTextPlugin('style.css', {

allChunks: true // 提取所有的chunk(默认只提取initial chunk,而上面CommonsChunkPlugin已经把部分抽离了)

})

解析(resolve)
这些选项能设置模块如何被解析。这里本骚年只讲两个常用的:

  1. resolve.extensions

自动解析确定的扩展。默认值为:

resolve: {

extensions: [".js", ".json"]

// 当我们需要使用typescript的时候,需要修改:

extensions: [".js", ".ts"]

// 当我们需要使用React时,还要修改:

extensions: ['.ts', '.tsx', '.js']

}
  1. resolve.alias

创建import或require的别名,来确保模块引入变得更简单。

resolve: {

alias: {

shared: path.resolve(__dirname, 'src/shared'),

}

}

如果使用typescript的话,我们还需要配置tsconfig.json:

{

"compilerOptions": {

"paths": {

"shared": ["src/shared"]

}

}

}

这样我们就可以跟长长的路径定位说拜拜了:

// 原代码

import {something} from '../../../../../shared/something';

// 配置后

import {something} from 'shared/something';

开发工具(devtool)
此选项控制是否生成,以及如何生成source map。
要开启source map,我们还需要安装source-map-loader:

npm i -D source-map-loader

同时添加loader的配置:

module.exports = {

module: {

rules: [

{

test: /\.js$/,

use: ["source-map-loader"],

enforce: "pre"

}

]

},

devtool: 'source-map' // 然后我们就可以开启了

};

webpack-dev-server
webpack-dev-server是webpack官方提供的一个小型Express服务器,主要提供两个功能:

为静态文件提供服务

自动刷新和热替换(HMR)

在实际开发中,webpack-dev-server可以实现以下需求:

每次修改代码后,webpack可以自动重新打包

浏览器可以响应代码变化并自动刷新

一般来说,我们可以通过引入webpack.config.js文件然后调整配置就可以启用了:

// webpackServer.config.js文件

// 生成配置

var webpack = require('webpack');

var path = require('path'); // 引入node的path库

var config = require("./webpack.config.js"); // 引入webpack.config.js

config.entry.concat(['webpack/hot/dev-server',

'webpack-dev-server/client?http://localhost:3333'

]);

module.exports = config;

然后命令行启动:

webpack-dev-server --config webpackServer.config.js --host 0.0.0.0 --port 3333 --devtool eval --progress --colors --hot --content-base dist

webpack-dev-server --config webpackServer.config.js后面的都是配置,也可以在webpackServer.config.js文件中写入。

常用配置:

devServer.contentBase: 告诉服务器从哪里提供内容

devServer.port(CLI): 指定要监听请求的端口号

devServer.host(CLI): 指定使用一个host。默认是localhost

devServer.hot: 启用webpack的模块热替换特性

devServer.progress(CLI): 将运行进度输出到控制台。

其余配置请移步官方文档。

前端框架与Webpack
这里本骚年就不一个个讲解了,简单分享几个用过的webpack.config.js配置吧。

Angular1 + Webpack

var webpack = require('webpack');

var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

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

var path = require('path');

var config = {

entry: {

app: ['./app/bootstrap.js'] // 入口文件

},

output: {

path: path.resolve(__dirname, 'app/entry'), // 编译后文件

publicPath: '/entry/',

filename: 'bundle.js' // 生成文件名

},

module: {

loaders: [{

test: /\.js$/,

loader: 'babel-loader',

exclude: /node_modules/

}]

},

plugins: [

// 使用CommonChunksLoader来提取公共模块

new CommonsChunkPlugin({

name: 'vendors',

filename: 'vendors.js',

minChunks: function (module) {

var userRequest = module.userRequest;

if (typeof userRequest !== 'string') {

return false;

}

return userRequest.indexOf('node_modules') >= 0

}

})

]

};

module.exports = config;

React + Webpack

var webpack = require('webpack');

var path = require('path'); //引入node的path库

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

var config = {

entry: ['webpack/hot/dev-server',

'webpack-dev-server/client?http://localhost:3000',

path.resolve(__dirname, 'app/index.js')

], //入口文件

output: {

path: path.resolve(__dirname, 'dist'), // 指定编译后的代码位置为 dist/bundle.js

filename: 'bundle.js'

},

module: {

loaders: [

// 为webpack指定loaders

{

test: /\.less$/,

loaders: ['style', 'css', 'less'],

include: path.resolve(__dirname, 'app')

},

{

test: /\.jsx?$/,

loader: 'babel-loader',

exclude: 'node_modules'

}

]

},

plugins: [

// 入口模板文件解析

new HtmlwebpackPlugin({

title: 'React Redux Test',

template: path.resolve(__dirname, 'templates/index.ejs'),

inject: 'body'

})

],

devtool: 'source-map'

}

module.exports = config;

Angular2 + Typescript + Webpack

var webpack = require('webpack');

var path = require('path');

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

var config = {

entry: ['babel-polyfill', './src/bootstrap.ts'],

output: {

path: path.resolve(__dirname, 'dist'),

filename: './bundle.js'

},

resolve: {

extensions: ['.ts', '.js']

},

module: {

rules: [

{

test: /\.ts$/,

use: ["babel-loader", "ts-loader", "angular2-template-loader"],

exclude: /node_modules/

},

{

test: /\.(html|css)$/,

use: ['raw-loader'],

exclude: [path.resolve(__dirname, 'src/index.html')]

},

{

test: /\.async\.(html|css)$/,

loaders: ['file?name=[name].[ext]']

}, {

test: /\.js$/,

use: ["source-map-loader"],

enforce: "pre"

}]

},

plugins: [

new HtmlwebpackPlugin({

template: path.resolve(__dirname, 'src/index.html'),

inject: 'body'

}),

new webpack.ContextReplacementPlugin(

/angular(\\|\/)core(\\|\/)@angular/,

path.resolve(__dirname, 'src'),

{}

)

],

devtool: 'source-map'

};

module.exports = config;

结束语
很可惜,当时玩Vue本骚年是用的vue-cli,所以这里没有Vue相关的代码。不过经过上面的讲解以及课后的练习,相信你一定可以搭建自己想要的应用。
Webpack的资源很多,而深入理解的你也能去开发自己想要的loader或是插件的,多了解多尝试总是很棒的。

文章来源:腾讯工程师 王贝珊

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容