本项目基于webpack 打包 vue 开发多页应用,涉及内容各种组件的相互关系,本地开发,测试环境的使用,生产环境的部署,其中包括eslint,webpack插件的使用,和移动端端像素问题,ajax的请求问题处理,内容错综复杂,当然在一个公司你只有部署一次就可以,一次学会10年无忧。
说的简单点就是手动搭建一个多页面脚手架
因此在阅读这篇文章。你需要提前具备或者安装好node npm 基础的东西,需要掌握webpack的基础。node 打包中的常用命令,就可以跟着我一步一步见证多页面是多么舒服的开发。
预期实现功能:
目录结构
MUZI // 项目名称
build // 放置webpack配置 文件夹
webpack.base.config.js // webpack配置 公用基础文件
webpack.dev.config.js // webpack配置 开发环境配置
webpack.pro.config.js // webpack配置 生产环境配置
webpack.test.config.js // webpack配置 测试环境配置
src //开发源文件 存放
assets // 存放公用的js和css文件
common // 存放公用和通用的js
http // 存放 关于请求的统一封装
utils // 公用的工具函数
index.js // 公用函数的具体文件 用index.js 命名,引入的时候可以省略。
index.js // ajax情况的公用处理
page-setup // 初始化的页面配置
index.js
common.js
css
common.less
components
directives
filters
page
demo
singe
.babelrc
package.json // 生产环境和开发环境需要的更重包管理
postcss.config.js // postcss 是帮我们后处理css
README.md // 记录开发 迭代事项
build 这个目录设计存放webpack的打包配置文件,设计思路,webpack 拆分不同环境打包,但是基础配置是一样的,我们单独拿出一个js文件编写基础配置,然后不同环境的打包方式基于基础js文件开发,方便升级维护,每个坏境特殊的地方,单独编写;
在操作过程中不够到包需要自己下载哦
node 基础补充:
path.join([…path]) 方法
path.join('/目录','目录1','..','src');
// 返回: ’/目录/src'
因此我们获取开发源码目录这样写,定义一个方法,方便管理,动态指定 源文件目录
let resolve = function (dir) {
return path.join(__dirname, '..', dir);
};
const srcPath = resolve('src'); //返回: /MUZI/src
动态获取子项目目录一级或者二级:
我们用match 非全局模式 正则使用贪心模式:
let s= 'src/page/margintrading/success/main.html'.match(/page\/(.+)\/main.html$/);
console.log(s[1]); // margintrading/success
let s= 'src/page/login/main.html'.match(/page\/(.+)\/main.html$/);
console.log(s[1]); // login
判断文件是否存在
使用node fs.existsSync 方法
fs.existsSync(path)
// 判断一个系统文件是否存在,存在返回true 不存在返回false
html-webpack-plugin插件
主要两个作用:一为html文件动态引入外部资源;二,创建html文件入口,我们是搭建多页面入口因此我们提供公用方法循环创建
打包之后文件管理
打包完成之后我们放到指定地方,如果不清理,会叠加很多,需要清理使用
CleanWebpackPlugin 进行处理
基础公用配置需要完成的事情
webpack.base.config.js
/*
* @Description: 公用基础配置
*/
const CleanWebpackPlugin = require('clean-webpack-plugin');
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const Uglifyjs = require('uglifyjs-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); // 压缩css
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const eslintFriendlyFormatter = require('eslint-friendly-formatter');
const MinCssExtractPlugin = require('mini-css-extract-plugin'); // 将CSS提取为独立的文件的插件
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
const glob = require('glob');
let path = require('path');
let resolve = function (dir) {
return path.join(__dirname, '..', dir);
};
let getBasicConfig = () => {
const srcPath = resolve('src');
let htmlfiles = glob.sync('src/page/**/main.html'),
plugins = [],
entry = {};
// 遍历page文件夹下面的所有main.html文件
htmlfiles.forEach(function (item, i) {
const chunkMatcher = item.match(/page\/(.+)\/main.html$/);
const chunkName = chunkMatcher[1];
// es6 模板语法 反引号`
const mainJSPath = `${srcPath}/page/${chunkName}/main.js`;
const chunkConfigPath = `${srcPath}/page/${chunkName}/chunk-config.js`;
// 默认设置页面无入口模块
let hasMainChunk = false;
// 如果入口脚本存在,则生成入口chunk
if (fs.existsSync(mainJSPath)) {
hasMainChunk = true;
entry[chunkName] = './src/page/' + chunkName + '/main.js';
}
let excludeConfig = [];
// 如果有exclude配置文件,去读取excludeChunk的配置
if (fs.existsSync(chunkConfigPath)) {
excludeConfig = require(chunkConfigPath).excludeChunks;
}
plugins.push(
new HtmlWebpackPlugin({
template: item,
filename: chunkName + '.html',
//
includeSiblingChunks: true,
//
appropriate: true,
inject: true,
chunks: hasMainChunk ? ['vendor', 'common', chunkName, 'vue-vendor'] : [],
excludeChunks: excludeConfig,
chunksSortMode: 'manual',
minify: {
removeComments: true,
collapseWhitespace: true
},
hash: false
})
);
});
plugins.push(
// 打包目标目录清理插件
new CleanWebpackPlugin('dist/**/*', {
root: path.resolve(__dirname, '..'),
// false 全部删除 @true 覆盖不删除 // 启用删除文件
dry: false
}),
new VueLoaderPlugin(),
new HappyPack({
// id 标识符,要和 rules 中指定的 id 对应起来
id: 'babel',
// 需要使用的 loader,用法和 rules 中 Loader 配置一样
// 可以直接是字符串,也可以是对象形式
loaders: ['babel-loader?cacheDirectory'],
// 共享进程池
threadPool: happyThreadPool
})
);
return [entry, plugins];
};
let getBasic = getBasicConfig();
module.exports = {
mode: process.env.NODE_ENV === 'dev' ? 'development' : 'production',
// 过上面的配置,我们就可以在业务代码中通过process.env.NODE_ENV拿到环境变量值
entry: getBasic[0],
module: {
rules: [
{
test: /\.(vue|js)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src')],
options: {
formatter: eslintFriendlyFormatter,
emitWarning: true
}
},
{
test: /\.js$/,
// use: ['babel-loader'],
use: ['happypack/loader?id=babel'],
exclude: resolve('node_modules')
},
{
test: /\.vue$/,
// loader: 'vue-loader',
use: [{
loader: 'vue-loader',
options: {
js: 'happypack/loader?id=babel'
}
}]
},
{
test: /\.css$/,
use: [
process.env.NODE_ENV === 'dev' ? 'style-loader' : {
loader: MinCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
// publicPath: './'
}
},
{
loader: 'css-loader',
options: {
minimize: true
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
process.env.NODE_ENV === 'dev' ? 'style-loader' : {
loader: MinCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
// publicPath: './'
}
},
{loader: 'css-loader'},
'postcss-loader',
{loader: 'less-loader'},
{
loader: 'sass-resources-loader',
options: {
resources: [
resolve('src/assets/css/common.less')
]
}
}
]
}
]
},
optimization: {
minimize: true,
// 合并重复的代码块
mergeDuplicateChunks: true,
// 移除父模块中已经存在的模块
removeAvailableModules: true,
// runtimeChunk: 'single',
// 无论 mode 值是什么始终保持文件名
// occurrenceOrder: true,
// 代码拆分,目前HtmlWebpackPlugin不支持,暂不开启
namedModules: true,
namedChunks: true,
splitChunks: {
minSize: 3000,
// chunks: 'async',
minChunks: 2,
name: true,
cacheGroups: {
vue: {
name: 'vue-vendor', // 拆分块的名称
chunks: 'initial', // initial(初始块)、async(按需加载块)、all(全部块),默认为all;
priority: 50, // 该配置项是设置处理的优先级,数值越大越优先处理
test: /([\/]node_modules[\/]vue)/,
enforce: true // 如果cacheGroup中没有设置minSize,则据此判断是否使用上层的minSize,true:则使用0,false:使用上层minSize
// minSize: 1024*10, //表示在压缩前的最小模块大小,默认为0;
// minChunks: 1, //表示被引用次数,默认为1;
// maxAsyncRequests: //最大的按需(异步)加载次数,默认为1;
// maxInitialRequests: //最大的初始化加载次数,默认为1;
// reuseExistingChunk: true //表示可以使用已经存在的块,即如果满足条件的块已经存在就使用已有的,不再创建一个新的块。
},
vendor: {
name: 'vendor',
chunks: 'initial',
priority: 40,
// reuseExistingChunk: false,
test: /[\\/]node_modules[\\/]/,
enforce: true
},
common: {
name: 'common',
chunks: 'initial',
// chunks: 'all',
// test:/\[\]/,
priority: 30,
minChunks: 2,
enforce: true
}
}
},
minimizer: [
// CSS资源优化
new OptimizeCssAssetsPlugin({
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', {discardComments: {removeAll: true}}]
},
canPrint: true
})
]
},
plugins: getBasic[1],
devtool: 'source-map'
};
本地开发环境需要实现的
完成之后本地开发就是运行起来
/**
* @Description: 本地开发文件配置
*/
const webpack = require('webpack');
const merge = require('webpack-merge');
const baseconfig = require('./webpack.base.config');
const path = require('path');
// 开发路径配置
const publicPath = '/';
function resolve (dir) {
return path.join(__dirname, '..', dir);
}
let plugins = [];// 插件入口
plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
);
let devConfig = {
output: {
filename: 'static/js/[name].[hash:8].js',
chunkFilename: 'static/lib/[name].[hash:8].js',
hashDigestLength: 10,
// path: resolve(packageJson.name),
path: publicPath,
publicPath
},
resolve: {
extensions: ['.vue', '.js', '.css', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
// 设置配置模块,引用当前环境指定的配置
'@config$': resolve('/src/config/dev.js')
}
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240, // 图片文件大小小于limit值;转换成base64
publicPath,
name: 'static/images/[name].[ext]?[hash:8]'
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 1000,
publicPath,
name: 'static/fonts/[name].[ext]?[hash]'
}
}
]
},
devtool: 'source-map',
devServer: {
publicPath: `/${publicPath}/`,
host: '开发的IP地址',
compress: true,
port: 1443,
hot: true,
// 代理地址配置
proxy: {
'/MUZI': {
target: '代理接口地址',
changeOrigin: true,
pathRewrite: {
'^/MUZI': '/MUZI'
}
}
},
allowedHosts: ['localhost', '0.0.0.0', '127.0.0.1','*'],
// 是否自动打开浏览器
open: false,
openPage: `/index.html`
},
plugins: plugins
// 启动本地服务相关配置
};
module.exports = merge(baseconfig, devConfig);
和开发环境差不多只是多加一些压缩插件,去掉本地服务
/*
生产环境配置
*/
const merge = require('webpack-merge');
const Uglifyjs = require('uglifyjs-webpack-plugin');
const MinCssExtractPlugin = require('mini-css-extract-plugin'); // 将CSS提取为独立的文件的插件
const baseconfig = require('./webpack.base.config');
let path = require('path');
const publicPath = `域名地址`;
function resolve (dir) {
return path.join(__dirname, '..', dir);
}
let plugins = [], // 插件组件
entry = {};// webpack文件入口
plugins.push(
new MinCssExtractPlugin({
filename: 'static/css/[name].[hash:8].css',
chunkFilename: 'static/css/lib/[id].[hash:8].css',
minify: {
// 把页面中的注释去掉
removeComments: true,
// 把多余的空格去掉
collapseWhitespace: true
}
}),
// new ImageminPlugin({
// pngquant: {
// quality: 95 - 100
// }
// }),
new Uglifyjs({
parallel: true,
sourceMap: true,
cache: true
})
);
let prdConfig = {
entry: entry,
output: {
filename: 'static/js/[name].[hash:8].js',
chunkFilename: 'static/lib/[name].[hash:8].js',
hashDigestLength: 10,
// path: resolve('dist/' + packageJson.name),
path: resolve('dist/mobileHall/'),
publicPath
},
resolve: {
extensions: ['.vue', '.js', '.css', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'@config$': resolve('/src/config/prd.js')
}
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240, // 图片文件大小小于limit值;转换成base64
publicPath,
name: 'static/images/[name].[hash:8].[ext]'
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 1000,
publicPath,
name: 'static/fonts/[name].[hash].[ext]'
}
}
]
},
plugins: plugins,
performance: {
hints: 'warning', // 枚举
maxAssetSize: 30000000, // 整数类型(以字节为单位)
maxEntrypointSize: 50000000, // 整数类型(以字节为单位)
assetFilter: function (assetFilename) {
// 提供资源文件名的断言函数
return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
}
}
};
module.exports = merge(baseconfig, prdConfig);
到这里,webpack 到基本配置完成,下一篇在继续说明其他公用文件到使用配置