webpack4 多页面应用 多环境配置

webpack4 多页面 多环境配置

使用 webpack4 + 原生html ,搭建多页面应用(例如:app配套的活动项目)

项目目录:

build
  |-envs.js
  |-rules.js
  |-webpack.base.conf.js
  |-webpack.dev.conf.js
  |-webpack.prod.conf.js
  |-webpack.test.conf.js
src
  |-commom
    |-css
    |-js
  |-pages
    |-home
      |-home.js
      |-index.html
      |-index.js
      |-index.scss
  |-static
    |-images
      |-home
        |-1.png
oss.js
postcss.config.js
package.json

主要配置 build 中的文件

一、envs.js

多环境

'use strict'

const path = require('path')
/*
 * 环境列表,第一个环境为默认环境
 * envName: 指明现在使用的环境
 * dirName: 打包的路径,只在build的时候有用
 * baseUrl: 这个环境下面的api 请求的域名
 * assetsPublicPath: 静态资源存放的域名,未指定则使用相对路径
 * */
const ENV_LIST = [
    {
        // 本地开发环境
        envName: 'dev',
        dirName: 'dev',
        baseUrl: '',
        assetsPublicPath: '/'
    },
    {
        // 测试环境
        envName: 'test',
        dirName: path.resolve(__dirname, '../dist_h5'),
        baseUrl: '',
        assetsPublicPath: '/'
    },
    {
        // 生产环境(命令行参数(process.arg)中prod是保留字,所以使用pro)
        envName: 'pro',
        dirName: path.resolve(__dirname, '../dist_h5'),
        baseUrl: '',
        assetsPublicPath: './'
    },

]

//获取环境,即--mode后的环境
const HOST_ENV = JSON.parse(process.env.npm_config_argv).original[3] || ""
//没有设置环境,则默认为第一个
const HOST_CONF = HOST_ENV ? ENV_LIST.find(item => item.envName === HOST_ENV) : ENV_LIST[0]
// 把环境常量挂载到process.env方便客户端使用
process.env.BASE_URL = HOST_CONF.baseUrl

module.exports.HOST_CONF = HOST_CONF
module.exports.ENV_LIST = ENV_LIST

二、rules.js

公共工具

const extractTextPlugin = require("extract-text-webpack-plugin");
const rules = [{
	test: /\.(css|scss|sass)$/,
	// 区别开发环境和生成环境
	use: process.env.NODE_ENV === "development" ? ["style-loader", "css-loader", "sass-loader", "postcss-loader"] : extractTextPlugin
		.extract({
			fallback: "style-loader",
			use: ["css-loader", "sass-loader", "postcss-loader"],
			// css中的基础路径
			publicPath: "../"
		})
},
{
	test: /\.js$/,
	use: [{
		loader: "babel-loader"
	}],
	// 不检查node_modules下的js文件
	// exclude: "/node_modules/"
}, {
	test: /\.(png|jpg|gif)$/,
	use: [{
		// 需要下载url-loader
		loader: "url-loader?limit=100&outputPath=./static/images&name=[name].[ext]",
		//新版本的url-loader的limit属性有时无效,故隐藏下方属性
		// options: {
		//   limit: 5000 , //小于这个时将会已base64位图片打包处理
		//   // 图片文件输出的文件夹
		//   publicPath: "./",
		//   outputPath: "./static/images"
		// }
	}]
},
{
	test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
	use: [{
		// 需要下载url-loader
		loader: 'file-loader?limit=100&outputPath=./static/audio&name=[name].[ext]&publicPath=./static/audio',
	}]

},
{
	test: /\.html$/,
	// html中的img标签
	use: {
		loader: 'html-loader',
		options: {
			attrs: ['img:src', 'img:data-src', 'audio:src'],
			minimize: true
		}
	}
},
	// {
	//         test: /\.mp3$/i,
	// 		use: 'file-loader'
	//       },
];
module.exports = rules;

三、webpack.base.conf.js

const path = require('path');
const webpack = require("webpack");
const glob = require("glob");

require("./envs");
//消除冗余的css
const purifyCssWebpack = require("purifycss-webpack");
// html模板
const htmlWebpackPlugin = require("html-webpack-plugin");
//静态资源输出
const copyWebpackPlugin = require("copy-webpack-plugin");
const rules = require("./rules");

// 获取html-webpack-plugin参数的方法
// 配合htmlwebpackplugin,htmlwebpackplugin需要一些配置,而多页面应用就需要产出多个同配置但是不同名的html文件,这个方法就是用传入的参数而产生不同的页面名配置。
function getHtmlConfig(name, chunks) {
    return {
        template: `./src/pages/${name}/index.html`,
        filename: process.env.NODE_ENV === "development" ? `${name.slice(name.lastIndexOf('/') + 1)}.html` : `${name.slice(name.lastIndexOf('/') + 1)}.html`,
        inject: true,
        hash: false, //开启hash  ?[hash]
        chunks: chunks,
        minify: process.env.NODE_ENV === "development" ? false : {
            removeComments: true, //移除HTML中的注释
            collapseWhitespace: true, //折叠空白区域 也就是压缩代码
            removeAttributeQuotes: true, //去除属性引用
        },
    };
};

// 获取到我们pages下各个页面的index.js,然后返回一个对象,不用手动添加
function getEntry() {
    var entry = {};
    //读取src目录所有page入口
    glob.sync('./src/pages/**/index.js')
        .forEach(function (name) {
            var start = name.indexOf('src/') + 4,
                end = name.length - 3;
            var eArr = [];
            var n = name.slice(start, end);
            n = n.slice(0, n.lastIndexOf('/')); //保存各个组件的入口
            n = n.split('pages/')[1];
            eArr.push(name);
            entry[n] = eArr;
        });
    return entry;
};

module.exports = {
    entry: getEntry(),
    module: {
        rules: [...rules]
    },
    resolve: {
        alias: {
            '@': path.resolve(__dirname, '../src')
        }
    },
    //将外部变量或者模块加载进来
    externals: {
        // 'jquery': 'window.jQuery'
    },
    // 提取公共代码
    optimization: {
        splitChunks: {
            cacheGroups: {
                vendor: { // 抽离第三方插件
                    test: /node_modules/, // 指定是node_modules下的第三方包
                    chunks: 'initial',
                    name: 'vendor', // 打包后的文件名,任意命名
                    // 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
                    priority: 10
                },
                utils: { // 抽离自己写的公共代码
                    chunks: 'initial',
                    name: 'common', // 任意命名
                    minSize: 0, // 只要超出0字节就生成一个新包
                    minChunks: 2
                }
            }
        }
    },
    plugins: [
        //静态资源输出
        new copyWebpackPlugin([{
            from: path.resolve(__dirname, "../src/static"),
            to: './static',
            ignore: ['.*']
        }]),
        // 消除冗余的css代码
        new purifyCssWebpack({
            paths: glob.sync(path.join(__dirname, "../src/pages/*/*.html"))
        })
    ]
}

//配置页面
const entryObj = getEntry();
const htmlArray = [];
Object.keys(entryObj).forEach(element => {
    htmlArray.push({
        _html: element,
        title: '',
        chunks: ['vendor', 'common', element]
    })
})

//自动生成html模板
htmlArray.forEach((element) => {
    module.exports.plugins.push(new htmlWebpackPlugin(getHtmlConfig(element._html, element.chunks)));
})

四、webpack.dev.conf.js

本地开发环境

const path = require('path');
const webpack = require("webpack");
const merge = require("webpack-merge");
const webpackConfigBase = require('./webpack.base.conf');

const webpackConfigDev = {
    mode: 'development', // 通过 mode 声明开发环境
    output: {
        path: path.resolve(__dirname, '../dist_h5'),
        // 打包多出口文件
        filename: 'js/[name].[hash:5].js'
    },
    devServer: {
        contentBase: path.join(__dirname, "../src/pages/index"),
        publicPath: '/',
        host: "0.0.0.0", //这里修改自己电脑ip
        port: "9088",
        overlay: true, // 浏览器页面上显示错误
        // open: true, // 开启浏览器
        // stats: "errors-only", //stats: "errors-only"表示只打印错误:
        //服务器代理配置项
        proxy: {
            '/testing/*': {
                target: '',
                secure: true,
                changeOrigin: true
            }
        }
    }
}
module.exports = merge(webpackConfigBase, webpackConfigDev);

五、webpack.test.conf.js

线上测试环境

const path = require('path');
const webpack = require("webpack");
const merge = require("webpack-merge");
// 清除目录等
const cleanWebpackPlugin = require("clean-webpack-plugin");
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const extractTextPlugin = require("extract-text-webpack-plugin");
const webpackConfigBase = require('./webpack.base.conf');
const WebpackAliyunOss = require('webpack-aliyun-oss')
const Oss = require('../oss')

const webpackConfigProd = {
	mode: 'development', // 通过 mode 声明生产环境

	output: {
		path: path.resolve(__dirname, '../dist_h5'),
		// 打包多出口文件
		filename: 'js/[name].[hash].js',
		publicPath: './'
	},

	// 将js代码转化为字符串,此操作会影响后续js代码删除console
	// devtool: 'cheap-module-eval-source-map',

	plugins: [
		//删除dist_h5目录
		new cleanWebpackPlugin(['dist_h5'], {
			root: path.resolve(__dirname, '../'), //根目录
			verbose: true, //开启在控制台输出信息
			dry: false,
		}),
		new webpack.DefinePlugin({
			'process.env.BASE_URL': '\"' + process.env.BASE_URL + '\"'
		}),
		// 分离css插件参数为提取出去的路径
		new extractTextPlugin({
			filename: 'css/[name].[hash:8].min.css',
		}),
		//压缩css
		new OptimizeCSSPlugin({
			cssProcessorOptions: {
				safe: true
			}
		}),

        // 自动上传至阿里云服务器
		new WebpackAliyunOss({
			from: './dist_h5/**',
			dist: '/ost/test/',
			region: Oss.region,
			accessKeyId: Oss.accessKeyId,
			accessKeySecret: Oss.accessKeySecret,
			bucket: Oss.bucket,
			// test: true,
			setOssPath: filePath => {
				// some operations to filePath
				const index = filePath.lastIndexOf('dist_h5')
                // 7为 dist_h5 字符串的长度
				const Path = filePath.substring(index + 7, filePath.length)
				return Path.replace(/\\/g, '/')
			},
			setHeaders: filePath => {
				// some operations to filePath
				// return false to use default header
				return {
					'Cache-Control': 'max-age=31536000'
				}
			}
		})
	]

}
module.exports = merge(webpackConfigBase, webpackConfigProd);

六、webpack.prod.conf.js

生产环境

const path = require('path');
const webpack = require("webpack");
const merge = require("webpack-merge");
// 清除目录等
const cleanWebpackPlugin = require("clean-webpack-plugin");
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const extractTextPlugin = require("extract-text-webpack-plugin");
const webpackConfigBase = require('./webpack.base.conf');

const webpackConfigProd = {
	mode: 'production', // 通过 mode 声明生产环境

	output: {
		path: path.resolve(__dirname, '../dist_h5'),
		// 打包多出口文件
		filename: 'js/[name].[hash].js',
		publicPath: './'
	},

	// 将js代码转化为字符串,此操作会影响后续js代码删除console
	// devtool: 'cheap-module-eval-source-map', 

	plugins: [
		//删除dist_h5目录
		new cleanWebpackPlugin(['dist_h5'], {
			root: path.resolve(__dirname, '../'), //根目录
			verbose: true, //开启在控制台输出信息
			dry: false,
		}),
		new webpack.DefinePlugin({
			'process.env.BASE_URL': '\"' + process.env.BASE_URL + '\"'
		}),
		// 分离css插件参数为提取出去的路径
		new extractTextPlugin({
			filename: 'css/[name].[hash:8].min.css',
		}),
		//压缩css
		new OptimizeCSSPlugin({
			cssProcessorOptions: {
				safe: true
			}
		}),
		// 上线压缩 去除console等信息webpack4.x之后去除了webpack.optimize.UglifyJsPlugin
		new UglifyJSPlugin({
			uglifyOptions: {
				output: {
					comments: false // 去除注释
				},
				compress: {
					warnings: false,
					drop_debugger: false,
					drop_console: true, // 去除console
				}
			}
		}),
	],

}
module.exports = merge(webpackConfigBase, webpackConfigProd);

七、package.json

包管理

{
  "name": "h5",
  "version": "1.0.8",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.conf.js",
    "build:test": "cross-env webpack --config build/webpack.test.conf.js",
    "build:pro": "cross-env webpack --config build/webpack.prod.conf.js"
  },
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^8.5.0",
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-3": "^6.24.1",
    "clean-webpack-plugin": "^0.1.19",
    "copy-webpack-plugin": "^4.6.0",
    "cross-env": "^5.1.5",
    "css-loader": "^3.2.0",
    "exports-loader": "^0.7.0",
    "extract-text-webpack-plugin": "^4.0.0-beta.0",
    "file-loader": "^1.1.11",
    "glob": "^7.1.3",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "html-withimg-loader": "^0.1.16",
    "live-server": "^1.2.0",
    "mini-css-extract-plugin": "^0.8.0",
    "node-sass": "^4.9.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-import": "^11.1.0",
    "postcss-loader": "^2.1.5",
    "postcss-plugin-px2rem": "^0.8.1",
    "postcss-url": "^7.3.2",
    "purify-css": "^1.2.5",
    "purifycss-webpack": "^0.7.0",
    "raw-loader": "^4.0.2",
    "sass-loader": "^7.0.1",
    "script-loader": "^0.7.2",
    "style-loader": "^0.21.0",
    "uglify-js": "^3.12.8",
    "uglifyjs-webpack-plugin": "^1.3.0",
    "url-loader": "^0.5.9",
    "webpack": "^4.29.6",
    "webpack-aliyun-oss": "^0.3.1",
    "webpack-bundle-analyzer": "^3.6.0",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.4",
    "webpack-merge": "^4.1.2"
  },
  "dependencies": {
    "axios": "^0.19.0",
    "callapp-lib": "^3.1.3",
    "jquery": "^3.5.1",
    "qrcode": "^1.4.4",
    "reset-css": "^5.0.1"
  },
  "browserslist": [
    "defaults",
    "not ie < 11",
    "last 2 versions",
    "> 1%",
    "iOS 7",
    "last 3 iOS versions"
  ]
}

八、postcss.config.js

rem布局配置

module.exports = {
    plugins: [
        //自动添加css前缀
        require('autoprefixer'),
        //转换rem  需 install postcss-plugin-px2rem
        require("postcss-plugin-px2rem")({ 'remUnit': 75, 'baseDpr': 2 })
    ]
};

九、oss.js

module.exports = {
  region: 'xxx',
  accessKeyId: 'xxxxxx',
  accessKeySecret: 'xxxxxxx',
  bucket: 'xxx'
}

十、使用

  • pages/home/index.html

    <!DOCTYPE html>
    <html lang="en" style="font-size: 100px;">
    
    <head>
    	<meta charset="UTF-8">
    	<meta name="viewport"
    		content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no, viewport-fit=cover" />
    	<meta http-equiv="X-UA-Compatible" content="ie=edge">
    	<meta name="apple-mobile-web-app-capable" content="yes">
    	<title>index</title>
    
    </head>
    
    <body>
    	<div class="container">
    		<div class="box">1111</div>
    
    		<div class="box2">33333</div>
    
    		<img src="../../static/images/home/1.png" alt="">
    
    	</div>
    
    </body>
    
    </html>
    
  • pages/home/index.scss

    body {
      margin: 0 auto;
      // max-width: 750px;
    }
    
    .container {
      .box {
        width: 187.5px;
        height: 187.5px;
        background-color: red;
        text-align: center;
        line-height: 187.5px;
        font-size: 30px;
        border: 10px solid #000;
      }
      .box2 {
        margin-top: 20px;
        width: 50%;
        height: 2rem;
        background-color: green;
        text-align: center;
        line-height: 187px;
        font-size: 30px;
      }
    }
    
    
  • pages/home/index.js

    import 'reset-css'
    import '@/common/js/rem.js'
    import './index.scss'
    import '@/common/css/common.css'
    
    import Home from './home'
    new Home()
    
  • pages/home/home.js

    import request from '../../common/js/request'
    
    function $$(id) {
      return document.querySelector(id)
    }
    
    const CONFIG = {
      api: '/promote/annual-festival-list',
    }
    
    /**
     * jsbrudge  js与ios交互
     **/
    function setupWebViewJavascriptBridge(callback) {
      if (window.WebViewJavascriptBridge) {
        callback(WebViewJavascriptBridge)
      } else {
        document.addEventListener(
          'WebViewJavascriptBridgeReady',
          function () {
            callback(WebViewJavascriptBridge)
          },
          false
        )
      }
    
      if (window.WVJBCallbacks) {
        return window.WVJBCallbacks.push(callback)
      }
      window.WVJBCallbacks = [callback]
      var WVJBIframe = document.createElement('iframe')
      WVJBIframe.style.display = 'none'
      WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'
      document.documentElement.appendChild(WVJBIframe)
      setTimeout(function () {
        document.documentElement.removeChild(WVJBIframe)
      }, 0)
    }
    
    class Home {
      constructor() {
        this.userInfo = null
        this.isLoading = false
        this.init()
      }
      init() {
        console.log('页面进入');
        console.log('process.env.NODE_ENV', process.env.NODE_ENV);
      }
    
      // 获取数据
      getData() {
        if (this.isLoading) return // 防抖处理
        const payLoad = {
          token: this.userInfo.token,
          type_id: this.tabSel
        }
        try {
          this.isLoading = true
          request.post(CONFIG.api, payLoad).then(res => {
            if (res.code != '0') {
              this.toast(res.msg)
              return
            }
            this.isLoading = false
            this.res = res.data
    
            const top3 = this.res.list.splice(0, 3)
            $$('.top3').innerHTML = this.getTop3(top3)
    
            const tempArr = this.res.list.splice(0, 7)
            this.showingList = tempArr
    
            $$('.list_container').innerHTML = this.getList(this.showingList)
            $$('.fourth_content').innerHTML = this.getSelf(this.res.my_info)
    
    
          }).catch(err => {
            console.log('err----', err);
            this.isLoading = false
          })
        } catch (error) {
    
        }
      }
    
      // 与客户端交互获取用户基本信息(token等)
      getInfo() {
        const that = this
        if (/android/i.test(navigator.userAgent)) {
          try {
            that.userInfo = window.android.getInfo()
            that.userInfo = eval('(' + that.userInfo + ')')
            that.getData()
          } catch (e) {
            console.log(e, 'android报错')
          }
        } else if (/ios|iphone|ipod|pad/i.test(navigator.userAgent)) {
          try {
            setupWebViewJavascriptBridge(function (bridge) {
              var data = 'hello'
              bridge.callHandler('getInfo', data, function (resp) {
                that.userInfo = resp
                that.getData()
                console.log(resp, '获取ios信息')
              })
            })
          } catch (e) {
            console.log(e, 'ios报错')
          }
        }
      }
    }
    
    export default Home
    
    
  • 运行命令 npm run dev,编译完成后在地址栏输入:http://localhost:9088/home.html,即可看到 home 页面

至此,使用webpack进行多页面配置,完成!!

功能包括:

  • 多页面应用开发配置
  • 本地、线上开发、生产,环境配置
  • 本地环境使用代理
  • 线上开发环境配置aliyun,代码自动上传至服务器
  • 生产环境代码优化(压缩、哈希等)
  • axios二次封装
  • js与app端交互(获取token等信息)
  • sass预编译
上一篇:webpack4 express中间件webpack-dev-middleware线上运行环境


下一篇:webpack4.X(入门到入土)