meri-design npm 传送门
webpack
唠两句
- 现代的前端开发可以说每个公司都会用到打包工具,尤为webpack最香;webpack上市时候相继打败了grunt、gulp,后来有个号称‘零’配置的parcel上市,但它还是没有打败webpack,由此可以看出webpack是真的很香;
- 有人说我用cli,基本不会去碰webpack,也对哈,不过我想说,你想要达到高级的效果,你就得碰碰webpack;
- 我自己有个习惯,用别人造的轮子,还不如自己造一个,这样自己才能学到更多的知识,网上有个笑话,“工作三年,经验五年——怎么来的?加班来的”,其实是他是自己造来的!
依赖项:
-
cross-env
设置/获取全局变量
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const WebpackBar = require('webpackbar');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
- MiniCssExtractPlugin 文本分离插件,分离.vue文件中的css
- CleanWebpackPlugin 清理垃圾文件,打包前清理上次打包后的记录
- WebpackBar 启动的时候打印一个进度条
- VueLoaderPlugin vue加载器
接下来咱们来看一下webpack配置中的常用参数:
一、entry
- 入口
入口有三种写法String Array Object
- 在meri-design中的多组件打包是将所有组件的js打包到一个js、css文件中,在开发者使用过程中以达到全局引入的效果
entry: {
index: './src/entry/multiple.js', // 入口文件
}
- multiple.js
// 引入组件并包装成key-value对象
import Button from '../components/Button';
const Components = {
Button
}
// 安装注册组件
const install = (Vue) => Object.keys(Components).forEach((name) => {
Vue.component(name, Components[name]);
});
// 支持使用标签的方式引入
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
Components.install = install;
export default Components;
二、output
- 出口
常用的有三个参数:-
path
输出到的文件夹
例:我要打包输出到根目录的dist文件夹下path: path.resolve(__dirname, 'dist')
-
filename
输出到文件夹里保存的文件名
例:我想要打包文件的名字为bundle.jsfilename: 'bundle.js'
,一般都用开发者开发中的名字filename: '[name].js'
-
publishPath
页面引用的路径前缀
一般都是相对路径和绝对路径的区别,不过有时候你想把静态资源放到其他服务器可以这样写publishPath: 'http://www.xxx.com/public/javascript'
-
- 在meri-design中为了发布成npm包以及遵循require、import导入和标签引入做了以下配置:
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
publicPath: '/',
library: 'meri-design',
libraryTarget: 'umd',
umdNamedDefine: true,
globalObject: 'this'
}
- filename: 'index.js' 输出到dist目录下的js叫index.js
- library: 'meri-design' 输出的包名叫meri-design,也就是require、import导入的模块名
- libraryTarget: 'umd' libraryTarget会生成不同umd的代码,可以只是commonjs标准的,也可以是指amd标准的,也可以只是通过script标签引入的
- umdNamedDefine: true 会对 UMD 的构建过程中的 AMD 模块进行命名,否则就使用匿名的 define
- globalObject: 'this' 全局对象指定为this,不然会找不到window对象
三、module
- 解析模块
使用webpack打包各种资源都需要相应的loader(加载器)去处理(解析、查找),如解析css:
- 解析css需要
style-loader css-loader
- 解析css预编译器less需要
style-loader css-loader less-loader
- 解析.vue文件中的less需要
vue-style-loader css-loader less-loader
,其中vue-style-loader
是vue官方出的,而非webpack官方
例在vue中解析less:
module: {
rules: [
{
test: /\.css/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less/,
use: ['vue-style-loader', 'css-loader', 'less-loader']
}
]
}
- 在meri-design中使用的是stylus
- 设置css(stylus)公共变量
const cssConfig = [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1
},
},
{loader: "postcss-loader"}
];
const stylusConfig = [
MiniCssExtractPlugin.loader,
'thread-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
},
},
{loader: "postcss-loader"},
{
loader: 'stylus-loader'
},
{
loader: 'style-resources-loader',
options: {
injector: 'prepend',
patterns: path.resolve(__dirname, 'src/assets/stylus/variables.styl'),
}
}
];
- module配置
module: {
rules: [
{
test: /\.css$/,
use: cssConfig,
},
{
test: /\.styl(us)?$/,
use: stylusConfig,
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.vue$/,
use: [
'thread-loader',
{
loader: 'vue-loader',
options: {
loaders: {
css: cssConfig,
stylus: stylusConfig,
},
preserveWhitespace: false, // 不要留空白
},
}],
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.js$/,
use: ['thread-loader', 'babel-loader'],
exclude: (file) => (
/node_modules/.test(file) && !/\.vue\.js/.test(file)
),
},
{
test: /\.svg$/,
use: ['babel-loader', 'vue-svg-loader'],
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.(png|jpe?g|gif|bmp)$/,
use: [{
loader: 'url-loader',
options: { // 配置图片编译路径
limit: 8192, // 小于8k将图片转换成base64
name: '[name].[ext]?[hash:8]',
outputPath: 'images/',
},
}],
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: { // 配置html中图片编译
minimize: true,
}
}]
}
]
}
}
- 这里需特别注意的是
style-resources-loader
,他是在stylus注入之前读取全局变量的插件,没有它不能获取到全局变量 - importLoaders 可选值【0 1 2】
- thread-loader 自动分配进程
- MiniCssExtractPlugin.loader 分离css
- babel-loader 使用babel处理js
面试题:url-loader与file-loader有啥区别?
四、resolve
- 词典翻译过来是解决,他要解决什么呢?要解决的是资源路径问题
resolve: { // 配置路径别名
extensions: ['.js', '.vue', '.styl'],
modules: [
'node_modules',
path.resolve(__dirname, 'src/assets'),
path.resolve(__dirname, 'src/components'),
path.resolve(__dirname, 'src/utils'),
]
}
- extensions import引入文件的时候不用加后缀
- modules 别名配置
面试题:在开发中怎么解决../、../../、../../../...?
五、plugins
- 插件
plugins: [
new webpack.BannerPlugin(`@meri-design ${TimeFn()}`),
new VueLoaderPlugin(), // vue加载器
new WebpackBar(),
// new CleanWebpackPlugin([path.join(__dirname, 'dist'), path.join(__dirname, 'lib')]),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({ // 分离css
filename: '[name].css',
})
]
- BannerPlugin webpack的内置插件,在打包输出的js的行头加上公共注释
六、devtool
- 开发所依赖的工具
devtool = 'source-map'
设置生成.map文件方便开发调试-
webpack官网截图
-
七、devServer
devServer = {
contentBase: path.join(__dirname, 'production'), // 将 dist 目录下的文件,作为可访问文件。
compress: true, // 开启Gzip压缩
// , host: 'localhost' // 设置服务器的ip地址,默认localhost
host: get_ip, // 设置服务器的ip地址,默认localhost
port: 3002, // 端口号
open: true, // 自动打开浏览器
hot: true,
overlay: { // 当出现编译器错误或警告时,就在网页上显示一层黑色的背景层和错误信息
errors: true,
},
disableHostCheck: true, // 不检查主机
// ,historyApiFallback: { // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 /
// rewrites: [{ from: /./, to: '/' }]
// }
}
- 开发所需的服务
八、optimization
- 最佳化,最优化——优化代码
optimization = { // 抽离第三方插件
splitChunks: {
chunks: 'all', // 必须三选一: "initial" | "all" | "async"(默认就是异步)
minSize: 10000, // 提高缓存利用率,这需要在http2/spdy
maxSize: 0, // 没有限制
minChunks: 3, // 共享最少的chunk数,使用次数超过这个值才会被提取
maxAsyncRequests: 5, // 最多的异步chunk数
maxInitialRequests: 5, // 最多的同步chunks数
name: true,
cacheGroups: { // 这里开始设置缓存的 chunks
vendor: { // key 为entry中定义的 入口名称,new webpack.ProvidePlugin中的库
test: /node_modules/, // 正则规则验证,如果符合就提取 chunk (指定是node_modules下的第三方包)
name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
enforce: true,
},
styles: {
test: /src\.(css|styl)$/,
name: 'main',
enforce: true,
},
},
},
runtimeChunk: { name: 'manifest' }, // 为每个入口提取出webpack runtime模块
}
九、完整配置
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 自动生成index.html
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 文本分离插件,分离js和css
const CleanWebpackPlugin = require('clean-webpack-plugin'); // 清理垃圾文件
const WebpackBar = require('webpackbar');
const VueLoaderPlugin = require('vue-loader/lib/plugin'); // vue加载器
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); // 为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source
/**
* 判断是生产环境还是开发环境
* @type {boolean}
* isProd为true表示生产
*/
const isProd = process.env.NODE_ENV === 'production';
// 获取本机ip
const get_ip = require('./get_ip')();
// 获取时间
const TimeFn = require('./get_time');
/**
* css和stylus开发、生产依赖
* 生产分离css
*/
const cssConfig = [
isProd ? MiniCssExtractPlugin.loader : 'vue-style-loader',
'thread-loader',
{
loader: 'css-loader',
options: {
sourceMap: !isProd,
},
},
'postcss-loader',
];
const stylusConfig = [
isProd ? MiniCssExtractPlugin.loader : 'vue-style-loader',
'thread-loader',
{
loader: 'css-loader',
options: {
sourceMap: !isProd,
},
},
{
loader: 'stylus-loader',
options: {
sourceMap: !isProd,
},
}, {
loader: 'style-resources-loader',
options: {
injector: 'prepend',
patterns: path.resolve(__dirname, 'src/assets/stylus/variables.styl'),
},
},
];
const config = {
entry: {
index: isProd ? ['core-js/stable', 'regenerator-runtime/runtime', './src/main.js'] : './src/main.js' // 入口文件
// index: ['core-js/stable', 'regenerator-runtime/runtime', './src/main.js'], // 入口文件
},
output: {
path: path.resolve(__dirname, 'production'),
filename: isProd ? 'javascript/[name].[hash:8].js' : '[name].js', // [name] 是entry的key
publicPath: isProd ? './' : '/',
},
module: {
rules: [
{
test: /\.css$/,
use: cssConfig,
},
{
test: /\.styl(us)?$/,
use: stylusConfig,
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.vue$/,
use: [
'thread-loader',
{
loader: 'vue-loader',
options: {
loaders: {
css: cssConfig,
stylus: stylusConfig,
},
preserveWhitespace: false, // 不要留空白
},
}],
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.js$/,
use: ['thread-loader', 'cache-loader', `babel-loader?cacheDirectory=${!isProd}`],
exclude: (file) => (
/node_modules/.test(file) && !/\.vue\.js/.test(file)
),
},
{
test: /\.svg$/,
use: ['thread-loader', 'babel-loader', 'vue-svg-loader'],
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.(png|jpe?g|gif|bmp)$/,
use: [
{
loader: 'url-loader',
options: { // 配置图片编译路径
limit: 8192, // 小于8k将图片转换成base64
name: '[name].[ext]?[hash:8]',
outputPath: 'images/',
},
}],
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: { // 配置html中图片编译
minimize: true,
},
}],
}
]
},
resolve: { // 配置路径别名
extensions: ['.js', '.vue', '.styl'], // import引入文件的时候不用加后缀
modules: [
'node_modules',
path.resolve(__dirname, 'src/assets'),
path.resolve(__dirname, 'src/docs'),
path.resolve(__dirname, 'src/utils'),
],
},
plugins: [
new webpack.BannerPlugin(`@meri-design ${TimeFn()}`),
new HardSourceWebpackPlugin(),
new VueLoaderPlugin(), // vue加载器
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.html'), // 引入模版
favicon: path.join(__dirname, 'src/assets/favicon.ico'),
filename: 'index.html',
minify: { // 对index.html压缩
collapseWhitespace: isProd, // 去掉index.html的空格
removeAttributeQuotes: isProd, // 去掉引号
},
hash: true, // 去掉上次浏览器的缓存(使浏览器每次获取到的是最新的html)
// ,chunks:['vendor','main'] // 在产出的html文件里面引入哪些代码块,里面的名字要跟entry里面key对应(一般用于多文件入口)
inlineSource: '.(js|css)',
}),
new WebpackBar(),
],
};
if (isProd) {
config.plugins.push(
new CleanWebpackPlugin([path.join(__dirname, 'production')]),
new MiniCssExtractPlugin({ // 分离css
filename: 'stylesheets/[name].[contenthash:8].css',
// chunkFilename: isProd?'stylesheets/[name].[contenthash:8].css':'[name].css'
})
);
config.optimization = { // 抽离第三方插件
splitChunks: {
chunks: 'all', // 必须三选一: "initial" | "all" | "async"(默认就是异步)
minSize: 10000, // 提高缓存利用率,这需要在http2/spdy
maxSize: 0, // 没有限制
minChunks: 3, // 共享最少的chunk数,使用次数超过这个值才会被提取
maxAsyncRequests: 5, // 最多的异步chunk数
maxInitialRequests: 5, // 最多的同步chunks数
name: true,
cacheGroups: { // 这里开始设置缓存的 chunks
vendor: { // key 为entry中定义的 入口名称,new webpack.ProvidePlugin中的库
test: /node_modules/, // 正则规则验证,如果符合就提取 chunk (指定是node_modules下的第三方包)
name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
enforce: true,
},
styles: {
test: /src\.(css|styl)$/,
name: 'main',
enforce: true,
},
},
},
runtimeChunk: { name: 'manifest' }, // 为每个入口提取出webpack runtime模块
};
} else {
config.devtool = 'source-map'; // 如果只用source-map开发环境出现错误定位源文件,生产环境会生成map文件
config.devServer = {
contentBase: path.join(__dirname, 'production'), // 将 dist 目录下的文件,作为可访问文件。
compress: true, // 开启Gzip压缩
// , host: 'localhost' // 设置服务器的ip地址,默认localhost
host: get_ip, // 设置服务器的ip地址,默认localhost
port: 3002, // 端口号
open: true, // 自动打开浏览器
hot: true,
overlay: { // 当出现编译器错误或警告时,就在网页上显示一层黑色的背景层和错误信息
errors: true,
},
disableHostCheck: true, // 不检查主机
// ,historyApiFallback: { // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 /
// rewrites: [{ from: /./, to: '/' }]
// }
};
}
module.exports = config;