什么是webpack
webpack是一个module bundler(模块打包工具),所谓的模块就是在平时的前端开发中,用到一些静态资源,如JavaScript、CSS、图片等文件,webpack就将这些静态资源文件称之为模块
webpack支持AMD和CommonJS,以及其他的一些模块系统,并且兼容多种JS书写规范,可以处理模块间的以来关系,所以具有更强大的JS模块化的功能,它能对静态资源进行统一的管理以及打包发布,在官网中用这张图片介绍:
它在很多地方都能替代Grunt和Gulp,因为它能够编译打包CSS,做CSS预处理,对JS的方言进行编译,打包图片,代码压缩等等。所以在我接触了webpack之后,就不太想用gulp了
为什么使用webpack
总结如下:
- 对 CommonJS 、AMD 、ES6的语法做了兼容;
- 对js、css、图片等资源文件都支持打包;
- 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持;
- 有独立的配置文件webpack.config.js;
- 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间;
- 支持 SourceUrls 和 SourceMaps,易于调试;
- 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活;
- webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快;
webpack主要是用于vue和React较多,其实它就非常像Browserify,但是将应用打包为多个文件。如果单页面应用有多个页面,那么用户只从下载对应页面的代码. 当他么访问到另一个页面,他们不需要重新下载通用的代码。
基于本人项目使用
vue webpack的配置文件的基本目录结构如下:
dev.env.js
config内的文件其实是服务于build的,大部分是定义一个变量export出去
module.exports = merge(prodEnv,{
NODE_ENV: '"development"'
})
prod.env.js
当开发时调取dev.env.js的开发环境配置,发布时调用prod.env.js的生产环境配置
index.js
build.js
该文件作用,即构建生产版本。package.json中的scripts的build就是node build/build.js,输入命令行npm run build对该文件进行编译生成生产环境的代码。
check-versions.js
该文件用于检测node和npm的版本,实现版本依赖
utils.js
utils是工具的意思,是一个用来处理css的文件。
loader: 'css-loader',options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader,loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader,postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',//Object.assign是es6语法的浅复制,后两者合并后复制完成赋值
options: Object.assign({},loaderOptions,{
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
//ExtractTextPlugin可提取出文本,代表首先使用上面处理的loaders,当未能正确引入时使用vue-style-loader
return ExtractTextPlugin.extract({
use: loaders,fallback: 'vue-style-loader',publicPath: '../../'
})
} else {
//返回vue-style-loader连接loaders的最终值
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),//需要css-loader 和 vue-style-loader
postcss: generateLoaders(),//需要css-loader和postcssLoader 和 vue-style-loader
less: generateLoaders('less'),//需要less-loader 和 vue-style-loader
sass: generateLoaders('sass',{ indentedSyntax: true }),//需要sass-loader 和 vue-style-loader
scss: generateLoaders('sass'),//需要sass-loader 和 vue-style-loader
stylus: generateLoaders('stylus'),//需要stylus-loader 和 vue-style-loader
styl: generateLoaders('stylus') //需要stylus-loader 和 vue-style-loader
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
//将各种css,less,sass等综合在一起得出结果输出output
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\.' + extension + '$'),use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
//发送跨平台通知系统
const notifier = require('node-notifier')
return (severity,errors) => {
if (severity !== 'error') return
//当报错时输出错误信息的标题,错误信息详情,副标题以及图标
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,message: severity + ': ' + error.name,subtitle: filename || '',icon: path.join(__dirname,'logo.png')
})
}
}
vue-loader.conf.js
该文件的主要作用就是处理.vue文件,解析这个文件中的每个语言块(template、script、style),转换成js可用的js模块。
webpack.base.conf.js
webpack.base.conf.js是开发和生产共同使用提出来的基础配置文件,主要实现配制入口,配置输出环境,配置模块resolve和插件等
- [获取文件路径]
- @param dir [文件名称]
_dirname为当前模块文件所在目录的绝对路径
@return 文件绝对路径
/
function resolve (dir) {
return path.join(__dirname,'..',dir)
}
const createLintingRule = () => ({
test: /.(js|vue)$/,loader: 'eslint-loader',enforce: 'pre',include: [resolve('src'),resolve('test')],options: {
formatter: require('eslint-friendly-formatter'),emitWarning: !config.dev.showEslintErrorsInOverlay
}
})
module.exports = {
context: path.resolve(__dirname,'../'),/**
- [入口文件配置]
*/
entry: {
/** - [入口文件路径,babel-polyfill是对es6语法的支持]
*/
app: ['babel-polyfill','./src/main.js'],login: ['babel-polyfill','./src/loginMain.js'],license: ['babel-polyfill','./src/licenseMain.js']
},/** - [文件导出配置]
*/
output: {
path: config.build.assetsRoot,filename: '[name].js',publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath //公共存放路径
},resolve: {
/** - [使用vue-loader将vue文件转化成js的模块]
*/
{
test: /.vue$/,loader: 'vue-loader',options: vueLoaderConfig
},/** - [通过babel-loader将js进行编译成es5/es6 文件]
*/
{
test: /.js$/,loader: 'babel-loader',resolve('test')]
},/** - [图片、音像、字体都使用url-loader进行处理,超过10000会编译成base64]
/
{
test: /.(png|jpe?g|gif|svg)(\?.)?$/,loader: 'url-loader',options: {
limit: 10000,name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},{
test: /.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.)?$/,name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},{
test: /.(woff2?|eot|ttf|otf)(\?.)?$/,name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},/** - [canvas 解析]
*/
{
test: path.resolve(${resolve('src')}/lib/jtopo.js
),loader: ['exports-loader?window.JTopo','script-loader']
}
]
},//以下选项是Node.js全局变量或模块,这里主要是防止webpack注入一些Node.js的东西到vue中
node: {
setImmediate: false,dgram: 'empty',fs: 'empty',net: 'empty',tls: 'empty',child_process: 'empty'
}
}
webpack.dev.conf.js
module.exports = new Promise((resolve,reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
//查找端口号
portfinder.getPort((err,port) => {
if (err) {
reject(err)
} else {
//端口被占用时就重新设置evn和devServer的端口
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
//友好地输出信息
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [Your application is running here: http://${devWebpackConfig.devServer.host}:${port}
],},onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
webpack.prod.conf.js
const env = require('../config/prod.env')
function resolveApp(relativePath) {
return path.resolve(relativePath);
}
const webpackConfig = merge(baseWebpackConfig,{
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,//开启调试的模式。默认为true
extract: true,usePostCSS: true
})
},devtool: config.build.productionSourceMap ? config.build.devtool : false,output: {
path: config.build.assetsRoot,filename: utils.assetsPath('js/[name].[chunkhash].js'),chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false //警告:true保留警告,false不保留
}
},sourceMap: config.build.productionSourceMap,parallel: true
}),// extract css into its own file
//抽取文本。比如打包之后的index页面有style插入,就是这个插件抽取出来的,减少请求
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),allChunks: true,}),//优化css的插件
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true,map: { inline: false } }
: { safe: true }
}),//html打包
new HtmlWebpackPlugin({
filename: config.build.index,//压缩
minify: {
removeComments: true,//删除注释
collapseWhitespace: true,//删除空格
removeAttributeQuotes: true //删除属性的引号
},chunks: ['vendor','manifest','app'],// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),new HtmlWebpackPlugin({
filename: config.build.login,minify: {
removeComments: true,collapseWhitespace: true,removeAttributeQuotes: true
},'login'],new HtmlWebpackPlugin({
filename: config.build.license,'license'],new HtmlWebpackPlugin({
filename: config.build.notfound,chunks: [],new HtmlWebpackPlugin({
filename: config.build.forbidden,new HtmlWebpackPlugin({
filename: config.build.internal,new HtmlWebpackPlugin({
filename: config.build.licenses,// keep module.id stable when vender modules does not change
new webpack.HashedModuleIdsPlugin(),// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),//抽取公共的模块,提升你的代码在浏览器中的执行速度。
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(dirname,'../node_modules')
) === 0
)
}
}),// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',minChunks: Infinity
}),// 预编译所有模块到一个闭包中,
new webpack.optimize.CommonsChunkPlugin({
name: 'app',async: 'vendor-async',children: true,minChunks: 3
}),//复制,比如打包完之后需要把打包的文件复制到dist里面
new CopyWebpackPlugin([
{
from: path.resolve(dirname,'../static'),to: config.build.assetsSubDirectory,ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',algorithm: 'gzip',test: new RegExp(
'\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),threshold: 10240,minRatio: 0.8
})
)
}
// 提供带 Content-Encoding 编码的压缩版的资源
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig