webpack 之vue多页面打包(一)(脚手架) 一招学会常年无忧

简要说明

本项目基于webpack 打包 vue 开发多页应用,涉及内容各种组件的相互关系,本地开发,测试环境的使用,生产环境的部署,其中包括eslint,webpack插件的使用,和移动端端像素问题,ajax的请求问题处理,内容错综复杂,当然在一个公司你只有部署一次就可以,一次学会10年无忧。
说的简单点就是手动搭建一个多页面脚手架
因此在阅读这篇文章。你需要提前具备或者安装好node npm 基础的东西,需要掌握webpack的基础。node 打包中的常用命令,就可以跟着我一步一步见证多页面是多么舒服的开发。
预期实现功能:

  1. 实现多页面打包
  2. 不同环境打包代码
  3. js 的公用代码处理机制
  4. css 的公用代码处理机制
  5. js,css,img压缩抽离机制
  6. eslint 的配置
  7. 打包环境优化配置happypack的使用
  8. 动态域名目录配置

VUE 项目多页面结构

目录结构

   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   			// 记录开发 迭代事项

来看一下图片结构
webpack 之vue多页面打包(一)(脚手架) 一招学会常年无忧_第1张图片

webpack 配置内容

build 这个目录设计存放webpack的打包配置文件,设计思路,webpack 拆分不同环境打包,但是基础配置是一样的,我们单独拿出一个js文件编写基础配置,然后不同环境的打包方式基于基础js文件开发,方便升级维护,每个坏境特殊的地方,单独编写;
在操作过程中不够到包需要自己下载哦

基础公用配置

node 基础补充:

  • require 加载文件的方法
  • fs 模块文件系统工具
  • os 模块提供了一些基本的系统操作函数
  • glob 模块内部使用了minimatch来匹配文件
  • path 模块提供了一些用于处理文件路径的小工具
  • 全局变量 __dirname 表示当前执行脚本所在的目录。

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 进行处理
基础公用配置需要完成的事情

  1. 入口到配置
  2. 打包代码清理
  3. 所有环境都用到到公用插件包括优化
  4. 公用css 到加载

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'
};

开发环境的配置

本地开发环境需要实现的

  1. 测试出口配置
  2. 本地服务器配置
  3. 设置配置模块,引用当前环境指定的配置
  4. 配置 source-map 调试
  5. 合并基础配置merge

完成之后本地开发就是运行起来

/**
 * @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 到基本配置完成,下一篇在继续说明其他公用文件到使用配置

你可能感兴趣的:(webpack实操)