xrksudy / webpack-for-vue

从零构建vue配置

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

从零开始构建基于webpack4.0的Vue项目脚手架(参考资料优先为英文文档),请按照步骤进行项目配置。

1.第一次打包

  1. npm init -y 初始化项目生成package.json 注: 自行配置git初始化
  2. npm i -D webpack webpack-cli安装webpack
  3. 新建build文件夹作为webpack脚手架配置目录
  4. 在build文件下新建webpack.prod.js文件,此文件为编译生产代码文件
  5. 新建src目录作为项目源代码目录
  6. 在src目录下新建index.js作为项目入口
  7. build/webpack.prod.js下导出配置对象,先配置入口及出口,添加如下代码
const path = require('path');
const entryPath = path.resolve(__dirname, '../src/index.js');
const outputPath = path.resolve(__dirname, '../dist');
module.exports = {
	entry: {
		app: entryPath
	},
	output: {
		path: outputPath,
		filename: 'js/[name].[hash:8].js',
    		publicPath: '/'
	},
}

释: entry为webpack的入口,output为webpack打包后的出口,其中pathfilename是必须要配置的,分别为出口路径和出口文件名。 filename[name]对应入口的键,默认为main,项目中为app[hash:8]是为文件名添加长度为8位的哈希值,便于缓存。

  1. package.json script中添加"build": "webpack --config=build/webpack.prod.js",然后在终端输入npm run build进行第一次打包。第一次打包结果
  2. 完成了第一次打包!但报了警告,意思告诉我们要添加modeproductiondevelopment的选项配置。在webpack.prod.js中添加mode: 'production',再次使用npm run build打包,警告完美去除,进入下一阶段 在这里插入图片描述

2.配置loader

  1. loader主要作用是将浏览器不识别的内容转换为浏览器可以识别的内容,例如通过babel-loaderes5+语法的js转换为es5语法。
  2. src目录下新建index.scss文件,可以随便写入sass样式,例如
#example {
	font-size: 15px;
}
  1. index.js中引入
import './index.scss';
  1. 此时使用npm run build会报错,因为webpack还不能识别scss文件和sass语法,npm i -D node-sass sass-loader css-loader postcss-loader autoprefixer安装相关依赖。 释: sass-loadersass语法转换为csscss-loader能识别css中的import()、 url()postcss-loader能根据各个浏览器平台提供css代码兼容方案, autoprefixer通过postcss-loader配置自动为css代码添加各个浏览器前缀 在这里插入图片描述
  2. 添加loader之前,我们先配置postcss-loader,项目根目录下创建postcss.config.js文件,添加如下代码
module.exports = {
	plugins: {
		autoprefixer: {}
	}
};
  1. webpack.prod.js添加loader
...(省略相同代码)
module.exports = {
	...(省略相同代码)
	module: {
		rules: [
			{
        			test: /\.scss$/,
        			use: [
					'css-loader', 'postcss-loader', 'sass-loader'
        			]
			},
		]
	}
}

释: 要在module.rules中添加loader,test为匹配的文件,可以用正则来判断文件后缀名,use是使用的loader loader的执行顺序是自下而上的,这里执行的顺序依次是sass-loaderpostcss-loadercss-loader

  1. 再次使用npm run build打包,打包成功!每一次的打包都激动人心~继续使用更多的loader来匹配项目中的实际情况。 在这里插入图片描述
  2. npm i -D @babel/core @babel/plugin-syntax-dynamic-import @babel/preset-env添加依赖。根目录下添加babel.config.js文件,添加如下代码
module.exports = {
  presets: [
    ['@babel/env']
  ],
  plugins: [
    '@babel/plugin-syntax-dynamic-import'
  ]
};

释: @babel/env可以根据目标浏览器来提供一系列预设的babel插件,此外还可以配置兼容目标浏览器的版本 @babel/plugin-syntax-dynamic-import插件可以识别js中动态引入的代码,如improt('./index.js')

  1. webpack.prod.js中添加babel-loader
...(省略相同代码)
module.exports = {
	...(省略相同代码)
	module: {
		rules: [
			{
   				test: /\.js$/,
   				use: 'babel-loader',
   				exclude: /node_modules/
			}
		]
	}
}

node_modules文件夹排除,此时我们可以识别js代码,继续添加loader

  1. src下添加App.vue,并在index.js中引入,此时打包必会报错,npm i -D vue-loader添加官方推荐的vue-loader 释: vue-loader能识别.vue文件,将其中的script scss提取出各自的文件,V15版本必须要引入VueLoaderPlugin插件(插件配置之后说明,我们先完成vue-loader的配置)
  2. webpack.prod.js中添加vue-loader
...(省略相同代码)
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
	...(省略相同代码)
	module: {
		rules: [
			...(省略相同代码)
			{
				test: /\.vue$/,
				use: [
					'vue-loader'
				]
			},
		]
	},
	plugins: [
		new VueLoaderPlugin(),
	]
}

再次打包,又是一次愉快的成功打包。

  1. npm i -D url-loader添加依赖 url-loader能处理图片、字体等文件类型,在webpack.prod.js中配置
...(省略相同代码)
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
	...(省略相同代码)
	module: {
		rules: [
			...(省略相同代码)
			{
  				test: /\.(png|jpg|gif|svg|ttf|eot|woff|otf)$/,
  				use: [
    				{
      					loader: 'url-loader',
      					options: {
        					name: 'assets/[name].[hash:8].[ext]'
      					}
    				}
  				]
			}
		]
	},
}

释: 打包在assets目录下,[name] [hash]之前已经说明,[ext]根据原有的文件后缀生成。

  1. 在原有loader基础上增加对css文件的loader支持,现全部配置如下
...(省略相同代码)
module.exports = {
	...(省略相同代码)
	module: {
		rules: [
			...(省略相同代码)
			{
        			test: /\.css$/,
        			use: [
					'css-loader', 'postcss-loader'
        			]
      			},
		]
	},
	...(省略相同代码)
}

现在大部分的loader已经配置完成,可以应对项目大部分的开发需要,继续进入下一阶段

3.配置plugin

  1. plugin主要是在webpack构建过程中提供并添加各样的功能。在loader中我们已经使用过vueloaderplugin,在plugins数组中都要实例化plugin

  2. npm i -D html-webpack-plugin添加依赖 释: 这个插件能根据源html文件生成同样的html文件,并将打包生成的js、css文件自动引入到生成的html文件中。

  3. public文件夹下添加index.html文件,并添加html-webpack-plugin配置

...(省略相同代码)
const path = require('path');
const htmlPath = path.resolve(__dirname, '../public/index.html');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	...(省略相同代码)
	plugins: [
		new VueLoaderPlugin(),
		new HtmlWebpackPlugin({
			template: htmlPath,
			filename: 'index.html',
			minify: true
		}),
	]
}
  1. 打包,生成的index.html自动引入了js文件,继续添加插件 在这里插入图片描述
  2. npm i -D mini-css-extract-plugin 此插件主要将css代码分离出来
...(省略相同代码)
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
	...(省略相同代码)
	module: {
		rules: [
			...(省略相同代码)
			{
        			test: /\.scss$/,
        			use: [
					MiniCssExtractPlugin.loader,
					'css-loader', 'postcss-loader', 'sass-loader'
        			]
			},
			{
        			test: /\.css$/,
        			use: [
					MiniCssExtractPlugin.loader,
					'css-loader', 'postcss-loader'
        			]
      			},
		]
	},
	plugins: [
		...(省略相同代码)
		new MiniCssExtractPlugin({
      			filename: "css/[name].[hash:8].css",
      			chunkFilename: "css/[name].[chunkhash:8].css"
		}),
	]
}

filename是根据源文件打包后文件名,chunkFilename是被分离的文件名,分离优化之后再说明 再次打包,结果如下,css代码被分离出到单独的文件夹中

在这里插入图片描述

  1. npm i -D clean-webpack-plugin添加依赖
...(省略相同代码)
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
	...(省略相同代码)
	plugins: [
		...(省略相同代码)
		new CleanWebpackPlugin(),
	]
}

现在我们每次打包,clean-webpack-plugin会先清除dist文件夹下所有的内容,进入下一阶段

4.优化配置

  1. 通过一系列的插件来优化打包结果。
  2. npm i -D uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin 分别是压缩js、css的插件
...(省略相同代码)
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
	...(省略相同代码)
	plugins: [
		...(省略相同代码)
		new OptimizeCssAssetsPlugin(),
		new UglifyJsPlugin({
			parallel: true,				// 开启多线程压缩
			cache: true,				// 使用缓存
			uglifyOptions: {
				warnings: false,          // 删除警告
				compress: {
					drop_console: true,     // 去除日志
					drop_debugger: true     // 去除debugger
				},
				output: {
					comments: false         // 去除注释
				}
			},
		})
	]
}

通过使用压缩插件前后的打包对比 使用插件压缩结果 在这里插入图片描述 因为目前的代码量不多,但还是能看到压缩插件对打包后文件体积的优化。

  1. npm i -D happypack thread-loader添加依赖 释: js的node环境是单线程,happypack主要通过开启多个进程来加快打包过程,并不是绝对加快,thread-loader也是同样的作用,需要加在被加速的loader的前面
...(省略相同代码)
const HappyPack = require('happypack');
const HappyThreadPool = HappyPack.ThreadPool({ size: 2 });

module.exports = {
	...(省略相同代码)
	module: {
		rules: [
			{
				test: /\.vue$/,
				use: [
					'thread-loader',
					'vue-loader'
				]
			},
			{
        			test: /\.scss$/,
        			use: [
					MiniCssExtractPlugin.loader,
					'css-loader', 'postcss-loader', 'sass-loader'
        			]
			},
			{
        			test: /\.css$/,
        			use: [
					MiniCssExtractPlugin.loader,
					'happypack/loader?id=css'
        			]
      			},
			{
        			test: /\.js$/,
        			use: 'happypack/loader?id=babel',
        			exclude: /node_modules/
			},
		]
	},
	plugins: [
		...(省略相同代码)
		new HappyPack({
      		id: 'css',
      		threadPool: HappyThreadPool,
			loaders: ['css-loader', 'postcss-loader'],
    		}),
		new HappyPack({
			id: 'babel',
      			loaders: ['babel-loader'],
      			threadPool: HappyThreadPool,
		})
	]
}

注: vue-loader v15已经不支持happypack,因此用thread-loader代替 happypack thread-loader目前最新版本不支持sass-loader 8.0.1,暂时不使用

  1. 配置dll来缓存第三方库,npm i -S vue vue-router vuex element-ui lodash axios添加依赖并在index.js中引入
import './index.scss';
import App from './App.vue';
import element from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import Vue from 'vue';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import axios from 'axios';
import _ from 'lodash';
Vue.use(element);
Vue.use(VueRouter);
Vue.use(Vuex);

new Vue({
	el: '#app',
	render: h => h(App)
})

现在打包结果如下在这里插入图片描述 可见项目体积变大了很多,很多第三方库不是我们经常改动的地方,每次打包不用每次都打包这块代码,除非要升级第三方库的版本。使用dllPlugin来预先打包第三方库。 在build文件夹下新建webpack.dll.js文件

const webpack = require('webpack');
const path = require('path');
const libPath = path.resolve(__dirname, '../libs');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
	mode: 'production',
	entry: {
		lib: [
			'vue', 
			'vue-router', 
			'vuex', 
			'axios', 
			'element-ui',
			'lodash'
		]
	},
	output: {
		path: libPath,
		filename: '[name].[hash:8].js',
		library: '[name]_library'
	},
	plugins: [
		new CleanWebpackPlugin(),
		new webpack.DllPlugin({
      			context: __dirname,
      			name: '[name]_library',
      			path: path.resolve(libPath, '[name]-manifest.json')
    		}),
		new UglifyJsPlugin({
			parallel: true,
			cache: true,
			uglifyOptions: {
				warnings: false,          // 删除警告
				compress: {
					drop_console: true,     // 去除日志
					drop_debugger: true     // 去除debugger
				},
				output: {
					comments: false         // 去除注释
				}
			},
		})
	],
}

package.json中添加script "build-dll": "webpack --config=build/webpack.dll.js" 现在通过npm run build-dll打包第三方库,在出口中的library是打包好的library标识,与dllPlugin里的name相同,并生成manifest.json文件,主要是标明library的模块连接。 最后在webpack.prod.js将打包好的第三方库来与其他包进行整合,npm i -D add-asset-html-webpack-plugin

...(省略相同代码)
const path = require('path');
const libPath = path.resolve(__dirname, '../libs');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
	...(省略相同代码)
	plugins: [
		new HtmlWebpackPlugin({
			template: htmlPath,
			filename: 'index.html',
			minify: true
		}),
		new webpack.DllReferencePlugin({
      			context: __dirname,
      			manifest: require('../libs/lib-manifest.json')
		}),
		new AddAssetHtmlPlugin({
      			filepath: path.resolve(libPath, '*.js'),
      			outputPath: 'js'
    		}),
	]
}

dllReferencePlugin是对第三方库manifest.json的引用,可以通过此文件排除已打包好的内容,add-asset-html-webpack-plugin能将打包好的第三方库复制到目标目录下。再次构建,使用dllPlugin前后的构建速度差异巨大 在这里插入图片描述 在这里插入图片描述

  1. 使用optimization来配置缓存组等优化配置
...(省略相同代码)
module.exports = {
	...(省略相同代码)
	optimization: {
		runtimeChunk: 'single',
		splitChunks: {
      		cacheGroups: {
        		vendors: {
          			name: "vendors",
          			test: /[\\/]node_modules[\\/]/,
          			chunks: "all",
          			priority: -10,
          			reuseExistingChunk: true,
				},
			}
		}
	}
}

释: runtimeChunk将各个chunk运行时文件单独打包出来 splitChunkscacheGroups开启缓存组,对node_modules进行缓存复用,同样作用于开发时配置。现在打包结果如下,进入下一阶段在这里插入图片描述

5.配置dev

  1. npm i -D style-loader webpack-dev-server添加依赖
  2. package.json scripts中添加"dev": "webpack-dev-server --progress --config=build/webpack.dev.js"
// webpack.dev.js
const path = require('path');
const entryPath = path.resolve(__dirname, '../src/index.js');
const outputPath = path.resolve(__dirname, '../dist');
const htmlPath = path.resolve(__dirname, '../public/index.html');
const HappyPack = require('happypack');
const HappyThreadPool = HappyPack.ThreadPool({ size: 2 });
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	mode: 'development',
	entry: {
		app: entryPath
	},
	output: {
		path: outputPath,
		filename: 'js/[name].[hash:8].js',
    	publicPath: '/'
	},
	devtool: '#cheap-module-eval-source-map',
	devServer: {
		port: 8088,
		open: true,
		overlay: true,
		hot: true
	},
	module: {
		rules: [
			{
				test: /\.vue$/,
				exclude: /node_modules/,
				use: [
					'vue-loader'
				]
			},
			{
				test: /\.css$/,
        			use: [
					'style-loader',
					'happypack/loader?id=css'
        			]
			},
			{
        			test: /\.scss$/,
        			use: [
					'style-loader',
					'css-loader', 
					'postcss-loader', 
					'sass-loader'
        			]
			},
			{
        			test: /\.js$/,
        			use: 'happypack/loader?id=babel',
        			exclude: /node_modules/
			},
			{
        			test: /\.(png|jpg|gif|svg|ttf|eot|woff|otf)$/,
        			use: [
          				{
            					loader: 'url-loader',
            					options: {
              						name: 'assets/[name].[hash:8].[ext]'
            					}
          				}
        			]
      			}
		]
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin(),
		new VueLoaderPlugin(),
		new HtmlWebpackPlugin({
			template: htmlPath,
			filename: 'index.html',
			minify: true
		}),
		new HappyPack({
      			id: 'css',
      			threadPool: HappyThreadPool,
			loaders: ['css-loader', 'postcss-loader'],
		}),
		new HappyPack({
			id: 'babel',
      			loaders: ['babel-loader'],
      			threadPool: HappyThreadPool,
		}),
	],
	optimization: {
		runtimeChunk: 'single',
		splitChunks: {
      		cacheGroups: {
        	vendors: {
          		name: "vendors",
          		test: /[\\/]node_modules[\\/]/,
          		chunks: "all",
          		priority: -10,
          		reuseExistingChunk: true,
				},
			}
		}
	}
}

释:主要devServer的配置,port为浏览器监听端口,open自动打开浏览器,overlay将错误和警告输出到浏览器上,hot配合HotModuleReplacementPlugin使用热更新 npm run dev便可以进行浏览器调试

6.合并配置

  1. 现在webpack.dev.jswebpack.prod.js有许多相同的配置,通过webpack-merge来合并配置简化代码
  2. npm i -D webpack-merge添加依赖
  3. build文件夹下新建webpack.base.js文件,提取webpack.dev.jswebpack.prod.js的相同配置到此文件中。(过程略)。
  4. 现各文件代码如下
// webpack.base.js
const path = require('path');
const entryPath = path.resolve(__dirname, '../src/index.js');
const outputPath = path.resolve(__dirname, '../dist');
const htmlPath = path.resolve(__dirname, '../public/index.html');

const HappyPack = require('happypack');
const HappyThreadPool = HappyPack.ThreadPool({ size: 2 });
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	entry: {
		app: entryPath
	},
	output: {
		path: outputPath,
		filename: 'js/[name].[hash:8].js',
    	publicPath: '/'
	},
	module: {
		rules: [
			{
        			test: /\.js$/,
        			use: 'happypack/loader?id=babel',
        			exclude: /node_modules/
			},
			{
        			test: /\.(png|jpg|gif|svg|ttf|eot|woff|otf)$/,
        			use: [
          				{
            					loader: 'url-loader',
            					options: {
              						name: 'assets/[name].[hash:8].[ext]'
            					}
          				}
        			]
      			}
		]
	},
	plugins: [
		new VueLoaderPlugin(),
		new HtmlWebpackPlugin({
			template: htmlPath,
			filename: 'index.html',
			minify: true
		}),
		new HappyPack({
      			id: 'css',
      			threadPool: HappyThreadPool,
			loaders: ['css-loader', 'postcss-loader'],
    		}),
		new HappyPack({
			id: 'babel',
      			loaders: ['babel-loader'],
      			threadPool: HappyThreadPool,
		}),
	],
	optimization: {
		runtimeChunk: 'single',
		splitChunks: {
      			cacheGroups: {
        			vendors: {
          				name: "vendors",
          				test: /[\\/]node_modules[\\/]/,
          				chunks: "all",
          				priority: -10,
          				reuseExistingChunk: true,
				},
			}
		}
	}
}
// webpack.dev.js
const webpackBaseConfig = require('./webpack.base');
const webpack = require('webpack');
const WebpackMerge = require('webpack-merge');

module.exports = WebpackMerge(webpackBaseConfig, {
	mode: 'development',
	devtool: '#cheap-module-eval-source-map',
	devServer: {
		port: 8088,
		open: true,
		overlay: true,
		hot: true
	},
	module: {
		rules: [
			{
				test: /\.vue$/,
				exclude: /node_modules/,
				use: [
					'vue-loader'
				]
			},
			{
				test: /\.css$/,
        			use: [
					'style-loader',
					'happypack/loader?id=css'
        			]
			},
			{
        			test: /\.scss$/,
        			use: [
					'style-loader',
					'css-loader', 
					'postcss-loader', 
					'sass-loader'
        			]
			},
		]
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin(),
	],
})
const path = require('path');
const libPath = path.resolve(__dirname, '../libs');
const webpackBaseConfig = require('./webpack.base');
const webpack = require('webpack');
const WebpackMerge = require('webpack-merge');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

module.exports = WebpackMerge(webpackBaseConfig, {
	mode: 'production',
	module: {
		rules: [
			{
				test: /\.vue$/,
				use: [
					'thread-loader',
					'vue-loader'
				]
			},
			{
        			test: /\.scss$/,
        			use: [
					MiniCssExtractPlugin.loader,
					'css-loader', 'postcss-loader', 'sass-loader'
        			]
			},
			{
        			test: /\.css$/,
        			use: [
					MiniCssExtractPlugin.loader,
					'happypack/loader?id=css'
        			]
      			}
		]
	},
	plugins: [
		new CleanWebpackPlugin(),
		new webpack.DllReferencePlugin({
      			context: __dirname,
      			manifest: require('../libs/lib-manifest.json')
		}),
		new AddAssetHtmlPlugin({
      			filepath: path.resolve(libPath, '*.js'),
      			outputPath: 'js'
    		}),
		new MiniCssExtractPlugin({
      			filename: "css/[name].[hash:8].css",
      			chunkFilename: "css/[name].[chunkhash:8].css"
		}),
		new OptimizeCssAssetsPlugin(),
		new UglifyJsPlugin({
			parallel: true,
			cache: true,
			uglifyOptions: {
				warnings: false,          // 删除警告
				compress: {
					drop_console: true,     // 去除日志
					drop_debugger: true     // 去除debugger
				},
				output: {
					comments: false         // 去除注释
				}
			},
		})
	]
})

7.eslint-loader

  1. npm i -D eslint eslint-laoder eslint-plugin-vue添加依赖 使用eslint-loader来进行代码检查
  2. 根目录下添加.eslintrc.js配置文件,编写基于个人实际情况的代码规范规则
  3. webpack.dev.js中加入eslint-loader,在开发中进行代码检查
...(省略相同代码)
module.exports = WebpackMerge(webpackBaseConfig, {
	...(省略相同代码)
	module: {
		rules: [
			...(省略相同代码)
			{
				test: /\.(vue|js|jsx)$/,
				enforce: 'pre',
				exclude: /node_modules/,
				loader: 'eslint-loader'
			}
		]
	},
	...(省略相同代码)
});

8.结语

目前是较为通用的webpack版本,开发者可以根据项目的实际情况和官方文档在进行深入优化配置。

About

从零构建vue配置


Languages

Language:JavaScript 92.3%Language:HTML 4.1%Language:Vue 3.2%Language:CSS 0.4%