美文网首页
Webpack 多环境代码打包(测试、预发、正式环境)

Webpack 多环境代码打包(测试、预发、正式环境)

作者: 硅谷干货 | 来源:发表于2022-05-25 23:52 被阅读0次

    在 package.json 文件的 scripts 中,会提供开发环境与生产环境两个命令。但是实际使用中会遇见 测试版、预发布版以及正式版代码相继发布的情况,这样反复更改服务器地址,偶尔忘记更改 url 会给工作带来很多不必要的麻烦。这样就需要在生产环境中配置 测试版本打包命令、预发布版本打包命令与正式版本打包命令。

    具体步骤如下:

    1. Package.json 文件中 增加命令行命令,并指定路径。

    "scripts": {
        "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",  //开发环境打包命令
        "start": "npm run dev",
        "test": "node build/build-test.js", //测试环境打包命令
        "pre": "node build/build-pre.js",  //预发布环境打包命令
        "build": "node build/build.js", //正式环境打包命令
      },
    

    2. 在 build 文件中添加相应文件

    image.png

    build-test.js

    /**
     * 测试版
     */
    
    'use strict'
    require('./check-versions')()
    
    process.env.NODE_ENV = 'production-test'
    
    const ora = require('ora')
    const rm = require('rimraf')
    const path = require('path')
    const chalk = require('chalk')
    const webpack = require('webpack')
    const config = require('../config')
    const webpackConfig = require('./webpack.prod.conf')
    
    const spinner = ora('building for production...')
    spinner.start()
    
    rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
      if (err) throw err
      webpack(webpackConfig, (err, stats) => {
        spinner.stop()
        if (err) throw err
        process.stdout.write(stats.toString({
          colors: true,
          modules: false,
          children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
          chunks: false,
          chunkModules: false
        }) + '\n\n')
    
        if (stats.hasErrors()) {
          console.log(chalk.red('  Build failed with errors.\n'))
          process.exit(1)
        }
    
        console.log(chalk.cyan('  Build complete.\n'))
        console.log(chalk.yellow(
          '  Tip: built files are meant to be served over an HTTP server.\n' +
          '  Opening index.html over file:// won\'t work.\n'
        ))
    
        console.log(chalk.yellow(
            '  Tip: built files are meant to be served over an HTTP server.\n' +
            ' '+ process.env.NODE_ENV
          ))
      })
    })
    

    build-pre.js

    /**
     * 预发布版
     */
    
    'use strict'
    require('./check-versions')()
    
    process.env.NODE_ENV = 'production-pre'
    
    const ora = require('ora')
    const rm = require('rimraf')
    const path = require('path')
    const chalk = require('chalk')
    const webpack = require('webpack')
    const config = require('../config')
    const webpackConfig = require('./webpack.prod.conf')
    
    const spinner = ora('building for production...')
    spinner.start()
    
    rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
      if (err) throw err
      webpack(webpackConfig, (err, stats) => {
        spinner.stop()
        if (err) throw err
        process.stdout.write(stats.toString({
          colors: true,
          modules: false,
          children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
          chunks: false,
          chunkModules: false
        }) + '\n\n')
    
        if (stats.hasErrors()) {
          console.log(chalk.red('  Build failed with errors.\n'))
          process.exit(1)
        }
    
        console.log(chalk.cyan('  Build complete.\n'))
        console.log(chalk.yellow(
          '  Tip: built files are meant to be served over an HTTP server.\n' +
          '  Opening index.html over file:// won\'t work.\n'
        ))
    
        console.log(chalk.yellow(
            '  Tip: built files are meant to be served over an HTTP server.\n' +
            ' '+ process.env.NODE_ENV
          ))
      })
    })
    

    build.js

    /**
     * 正式版
     */
    require('./check-versions')()
    
    process.env.NODE_ENV = 'production'
    
    var ora = require('ora')
    var rm = require('rimraf')
    var path = require('path')
    var chalk = require('chalk')
    var webpack = require('webpack')
    var config = require('../config')
    var webpackConfig = require('./webpack.prod.conf')
    
    var spinner = ora('building for production...')
    spinner.start()
    
    rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
      if (err) throw err
      webpack(webpackConfig, function (err, stats) {
        spinner.stop()
        if (err) throw err
        process.stdout.write(stats.toString({
          colors: true,
          modules: false,
          children: false,
          chunks: false,
          chunkModules: false
        }) + '\n\n')
    
        console.log(chalk.cyan('  Build complete.\n'))
        console.log(chalk.yellow(
          '  Tip: built files are meant to be served over an HTTP server.\n' +
          '  Opening index.html over file:// won\'t work.\n'
        ))
        console.log(chalk.yellow(
          '  Tip: built files are meant to be served over an HTTP server.\n' +
          ' '+ process.env.NODE_ENV
        ))
    
         //gulp 正式站 生成 zip文件,
         var fs = require('fs');
         function getPackageJsonVersion () {
           // 这里我们直接解析 json 文件而不是使用 require,这是因为 require 会缓存多次调用,这会导致版本号不会被更新掉
           return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
         };
         var gulp = require('gulp');
         var zip = require('gulp-zip');
         var  filename=(new Date()).toLocaleString().replace(/:/g,'');
         console.log(chalk.yellow(
           '  Tip: zipName.\n' +
           ' '+ filename +getPackageJsonVersion()
         ))
         //创建一个文件到V.txt 到 dist 目录
    
         fs.writeFile('./dist/ver.txt','版本号:'+getPackageJsonVersion(),function(err){  
          if(err)  
              return console.error(err);  
          console.log('写入文件成功');  
         });  
    
    
         gulp.task('zip', function() {
                  gulp.src(['dist/**','README.md'])
                 .pipe(zip(`Philips_production_${filename}_v${getPackageJsonVersion()}.zip`))
                 .pipe(gulp.dest('dist1'));
         });
        //  gulp.run('zip')
      })
    })
    

    3、在 config 文件中增加环境变量配置

    image.png

    prod-test.env.js 增加环境变量

    /**
     * 测试环境配置
     */
    
    module.exports = {
        NODE_ENV: '"production-test"'
    }
    

    prod-pre.env.js 增加环境变量

    /**
     * 预发环境配置
     */
    
    module.exports = {
        NODE_ENV: '"production-pre"'
    }
    

    prod.env.js

    /**
     * 生产环境配置(正式)
     */
    
    module.exports = {
        NODE_ENV: '"production"'
    }
    

    index.js

    'use strict'
    // Template version: 1.3.1
    // see http://vuejs-templates.github.io/webpack for documentation.
    
    'use strict'
    // Template version: 1.3.1
    // see http://vuejs-templates.github.io/webpack for documentation.
    
    const path = require('path')
    
    module.exports = {
      dev: {  // dev开发 环境
        evn:require('./dev.env'),
        // 静态资源文件夹
        assetsSubDirectory: 'static',
        // 发布路径
        assetsPublicPath: '/',
        //配置代理(可跨域)
        proxyTable: {
          '/api': {
            target: "http://sfe.crmclick.com",// 接口的域名
            // secure: false,  // 如果是https接口,需要配置这个参数
            changeOrigin: true,// 如果接口跨域,需要进行这个参数配置
            pathRewrite: {
              '^/api': '/'
            }
          },
          // 注意: '/api' 为匹配项,target 为被请求的地址,因为在 ajax 的 url 中加了前缀 '/api',而原本的接口是没有这个前缀的,
          //所以需要通过 pathRewrite 来重写地址,将前缀 '/api' 转为 '/'。如果本身的接口地址就有 '/api' 这种通用前缀,就可以把 pathRewrite 删掉。
        },
    
        // Various Dev Server settings
        host: 'localhost', // can be overwritten by process.env.HOST
        port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
        autoOpenBrowser: false,
        errorOverlay: true,
        notifyOnErrors: true,
        poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
    
    
        /**
         * Source Maps
         */
    
        // https://webpack.js.org/configuration/devtool/#development
        devtool: 'cheap-module-eval-source-map',
    
        // If you have problems debugging vue-files in devtools,
        // set this to false - it *may* help
        // https://vue-loader.vuejs.org/en/options.html#cachebusting
        cacheBusting: true,
    
        cssSourceMap: true
      },
    
      build: { // 测试 预发布 正式生产 环境
        evn:process.env.NODE_ENV == "production"
        ? require('./prod.env')
        : process.env.NODE_ENV == "production-pre"
        ? require('./prod-pre.env')
        : process.env.NODE_ENV == "production-test"
        ? require('./prod-test.env')
        : require('./prod-test.env'), // 使用 config/prod.env.js 中定义的编译环境
        // Template for index.html
        index: path.resolve(__dirname, '../dist/index.html'), // 编译输入的 index.html 文件
    
        // Paths
        assetsRoot: path.resolve(__dirname, '../dist'), // 编译输出的静态资源路径
        assetsSubDirectory: 'static', // 编译输出的二级目录
        assetsPublicPath: '/', // 编译发布的根目录,可配置为资源服务器域名或 CDN 域名
    
        /**
         * Source Maps
         */
    
        productionSourceMap: false, // 是否开启 cssSourceMap
        // https://webpack.js.org/configuration/devtool/#production
        devtool: '#source-map',
    
        // Gzip off by default as many popular static hosts such as
        // Surge or Netlify already gzip all static assets for you.
        // Before setting to `true`, make sure to:
        // npm install --save-dev compression-webpack-plugin
        productionGzip: false, // 是否开启 gzip
        productionGzipExtensions: ['js', 'css'], // 需要使用 gzip 压缩的文件扩展名
    
        // Run the build command with an extra argument to
        // View the bundle analyzer report after build finishes:
        // `npm run build --report`
        // Set to `true` or `false` to always turn it on or off
        bundleAnalyzerReport: process.env.npm_config_report
      }
    }
    

    4. 修改 build 文件夹下的 webpack.prod.conf.js

    image.png

    webpack.prod.conf.js

    "use strict";
    const path = require("path");
    const utils = require("./utils");
    const webpack = require("webpack");
    const config = require("../config");
    const merge = require("webpack-merge");
    const baseWebpackConfig = require("./webpack.base.conf");
    const CopyWebpackPlugin = require("copy-webpack-plugin");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const ExtractTextPlugin = require("extract-text-webpack-plugin");
    const OptimizeCSSPlugin = require("optimize-css-assets-webpack-plugin");
    const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
    
    // const env = require('../config/prod.env')
    
    console.log('------------------+'+process.env.NODE_ENV)
    
    const env =
      process.env.NODE_ENV === "production"
        ? require("../config/prod.env")
        : process.env.NODE_ENV === "production-test"
        ? require("../config/prod-test.env")
        : process.env.NODE_ENV === "production-pre"
        ? require("../config/prod-pre.env")
        : require("../config/dev.env");
    
    const webpackConfig = merge(baseWebpackConfig, {
      module: {
        rules: utils.styleLoaders({
          sourceMap: config.build.productionSourceMap,
          extract: true,
          usePostCSS: true
        })
      },
      devtool: config.build.productionSourceMap ? config.build.devtool : false,
      output: {
        path: config.build.assetsRoot,
        filename: utils.assetsPath("js/[name].[chunkhash].js"),
        chunkFilename: utils.assetsPath("js/[id].[chunkhash].js")
      },
      plugins: [
        // http://vuejs.github.io/vue-loader/en/workflow/production.html
        new webpack.DefinePlugin({
          "process.env": env
        }),
        new UglifyJsPlugin({
          uglifyOptions: {
            compress: {
              warnings: false
            }
          },
          sourceMap: config.build.productionSourceMap,
          parallel: true
        }),
        // extract css into its own file
        new ExtractTextPlugin({
          filename: utils.assetsPath("css/[name].[contenthash].css"),
          // Setting the following option to `false` will not extract CSS from codesplit chunks.
          // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
          // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
          // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
          allChunks: true
        }),
        // Compress extracted CSS. We are using this plugin so that possible
        // duplicated CSS from different components can be deduped.
        new OptimizeCSSPlugin({
          cssProcessorOptions: config.build.productionSourceMap
            ? { safe: true, map: { inline: false } }
            : { safe: true }
        }),
        // generate dist index.html with correct asset hash for caching.
        // you can customize output by editing /index.html
        // see https://github.com/ampedandwired/html-webpack-plugin
        new HtmlWebpackPlugin({
          filename: config.build.index,
          template: "index.html",
          inject: true,
          minify: {
            removeComments: true,
            collapseWhitespace: true,
            removeAttributeQuotes: true
            // more options:
            // https://github.com/kangax/html-minifier#options-quick-reference
          },
          // necessary to consistently work with multiple chunks via CommonsChunkPlugin
          chunksSortMode: "dependency"
        }),
        // keep module.id stable when vendor modules does not change
        new webpack.HashedModuleIdsPlugin(),
        // enable scope hoisting
        new webpack.optimize.ModuleConcatenationPlugin(),
        // split vendor js into its own file
        new webpack.optimize.CommonsChunkPlugin({
          name: "vendor",
          minChunks(module) {
            // any required modules inside node_modules are extracted to vendor
            return (
              module.resource &&
              /\.js$/.test(module.resource) &&
              module.resource.indexOf(path.join(__dirname, "../node_modules")) === 0
            );
          }
        }),
        // extract webpack runtime and module manifest to its own file in order to
        // prevent vendor hash from being updated whenever app bundle is updated
        new webpack.optimize.CommonsChunkPlugin({
          name: "manifest",
          minChunks: Infinity
        }),
        // This instance extracts shared chunks from code splitted chunks and bundles them
        // in a separate chunk, similar to the vendor chunk
        // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
        new webpack.optimize.CommonsChunkPlugin({
          name: "app",
          async: "vendor-async",
          children: true,
          minChunks: 3
        }),
    
        // copy custom static assets
        new CopyWebpackPlugin([
          {
            from: path.resolve(__dirname, "../static"),
            to: config.build.assetsSubDirectory,
            ignore: [".*"]
          }
        ])
      ]
    });
    
    if (config.build.productionGzip) {
      const CompressionWebpackPlugin = require("compression-webpack-plugin");
    
      webpackConfig.plugins.push(
        new CompressionWebpackPlugin({
          asset: "[path].gz[query]",
          algorithm: "gzip",
          test: new RegExp(
            "\\.(" + config.build.productionGzipExtensions.join("|") + ")$"
          ),
          threshold: 10240,
          minRatio: 0.8
        })
      );
    }
    
    if (config.build.bundleAnalyzerReport) {
      const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
        .BundleAnalyzerPlugin;
      webpackConfig.plugins.push(new BundleAnalyzerPlugin());
    }
    
    module.exports = webpackConfig;
    

    5. 修改 build 文件夹下的 webpack.base.conf.js

    webpack.base.conf.js

    "use strict";
    const path = require("path");
    const utils = require("./utils");
    const config = require("../config");
    const vueLoaderConfig = require("./vue-loader.conf");
    
    function resolve(dir) {
      return path.join(__dirname, "..", dir);
    }
    
    module.exports = {
      context: path.resolve(__dirname, "../"),
      entry: {
        app: "./src/main.js"
      },
      output: {
        path: config.build.assetsRoot,
        filename: "[name].js",
      publicPath:
         process.env.NODE_ENV === "development"
         ? config.dev.assetsPublicPath
         : config.build.assetsPublicPath
      },
      resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
          vue$: "vue/dist/vue.esm.js",
          "@": resolve("src")
        }
      },
      module: {
        rules: [
          {
            test: /\.vue$/,
            loader: "vue-loader",
            options: vueLoaderConfig
          },
          {
            test: /\.js$/,
            loader: "babel-loader",
            include: [
              resolve("src"),
              resolve("test"),
              resolve("node_modules/webpack-dev-server/client")
            ]
          },
          {
            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
            loader: "url-loader",
            options: {
              limit: 10000,
              name: utils.assetsPath("img/[name].[hash:7].[ext]")
            }
          },
          {
            test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
            loader: "url-loader",
            options: {
              limit: 10000,
              name: utils.assetsPath("media/[name].[hash:7].[ext]")
            }
          },
          {
            test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
            loader: "url-loader",
            options: {
              limit: 10000,
              name: utils.assetsPath("fonts/[name].[hash:7].[ext]")
            }
          },
          {
            test: /\.less$/,
            loader: "style-loader!css-loader!less-loader"
          }
        ]
      },
      node: {
        // prevent webpack from injecting useless setImmediate polyfill because Vue
        // source contains it (although only uses it if it's native).
        setImmediate: false,
        // prevent webpack from injecting mocks to Node native modules
        // that does not make sense for the client
        dgram: "empty",
        fs: "empty",
        net: "empty",
        tls: "empty",
        child_process: "empty"
      }
    };
    

    6. 创建一个公用的页面,用来增加环境变量判断 以及 对 axios 进行配置 & 拦截

    untils/config.js

    import axios from 'axios';
    import qs from 'qs';
    import VueRouter from 'vue-router';
    import routers from '../router';
    
    const router = new VueRouter({
        routers
    });
    
    // 设置接口地址
    let baseUrl = '';
    // 上传图片文件路径
    let uploadBaseUrl = '';
    // alert(process.env.NODE_ENV)
    if (process.env.NODE_ENV == 'development' || process.env.NODE_ENV == 'production-test') {
        // 开发/测试 环境
        baseUrl = 'https://sfe.crmclick.com';
        uploadBaseUrl = 'http://sfe.crmclick.com/uploadNoZip.aspx';
    }else if(process.env.NODE_ENV == 'production-pre'){
        // 预发 环境
        baseUrl = 'xxxx';
        uploadBaseUrl = 'xxxx';
    }else if(process.env.NODE_ENV == 'production'){
        // 正式环境
        baseUrl = 'xxxx';
        uploadBaseUrl = 'xxxx';
    }
    
    // axios 配置 & 拦截
    // 响应时间
    axios.defaults.timeout = 20000;
    // 是否允许携带cookie
    // withCredentials为true的情况下,后端要设置Access-Control-Allow-Origin为你的源地址,例如http://localhost:8080,不能是*,
    // 而且还要设置header('Access-Control-Allow-Credentials: true');              
    axios.defaults.withCredentials = false;
    // 配置请求头
    axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
    // POST传参序列化(添加请求拦截器)
    axios.interceptors.request.use((config) => {
        //在发送请求之前强制降低复杂请求
        if(config.method  === 'post'){
            config.data = qs.stringify(config.data);
        }
        return config;
    },(error) =>{
        return Promise.reject(error);
    });
    // 返回状态判断(添加响应拦截器)
    axios.interceptors.response.use((res) =>{
        //对响应数据做些事
        if(!res.data){
            return Promise.reject(res);
        }
    
        if(res.data.respCode == '111'){
            // 登陆 过期
            sessionStorage.removeItem('User');
            alert("登陆已失效,请重新登陆");
            router.go('/');
            return
        }
        return res;
    }, (error) => {
        return Promise.reject(error);
    });
    
    export {
        baseUrl,
        uploadBaseUrl,
    }
    

    7、在页面 import baseUrl axios 等 即可针对不同环境进行接口调用

    <script>
    import axios from 'axios';
    import { baseUrl } from '../untils/config';
    import HeaderMenu from './common/HeaderMenu';//引用子组件
    
    export default {
        name: 'Gift',
        data() {
            return {
                title:"礼品展示",
                PageIndex:1,
                PageSize:10,
                GiftList:[],
                loading:false,
                LoadMore:true,
            }
        },
        components: {
            "V-Menu": HeaderMenu,
        },
        created() {
            this.GetGiftList();
        },
        mounted() {
    
        },
        methods: {
            GetImg(url){
                return baseUrl + url;
            },
            GetGiftList() {
                axios.post(baseUrl+"/suzhou/Handler/Points.ashx",{
                    op: "GiftList", 
                    PageIndex: this.PageIndex, 
                    PageSize:this.PageSize
                }).then(res => {
                    if (res.data.state == 1) {
                        if(this.PageIndex==1){
                            this.GiftList = res.data.rows;
                        }else{
                            var newGiftArr = this.GiftList.slice(0);
                            if(res.data.rows.length>0){
                                for(var i=0;i<res.data.rows.length;i++){
                                    newGiftArr.push(res.data.rows[i]);
                                }
                            }
                            this.GiftList = newGiftArr;
                        }
                        if(res.data.rows.length < this.PageSize){
                            this.LoadMore = false;
                        }
                    }else if(res.data.rows == -1){
                        this.$toast.warning(res.data.msg);
                    }
                })
                .catch(error => {
                    debugger
                    this.$toast.error(error);
                });
            },
            load:function(){
                if(this.LoadMore){
                    this.loading = true;
                    setTimeout(function() {
                        this.loading = false;
                        this.PageIndex ++;
                        this.GetGiftList();
                    }, 2000)
                }
            },
        }
    }
    </script>
    

    8、打包命令如下

    npm run dev      开发环境
    npm run test     测试环境
    npm run pre      预发环境
    npm run build    正式环境
    

    点赞加关注,永远不迷路

    相关文章

      网友评论

          本文标题:Webpack 多环境代码打包(测试、预发、正式环境)

          本文链接:https://www.haomeiwen.com/subject/njayprtx.html