Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从零开始做Vue前端架构(5)webpack发布版配置 & 配置重构 #14

Open
CodeLittlePrince opened this issue Feb 1, 2018 · 0 comments

Comments

@CodeLittlePrince
Copy link
Owner

CodeLittlePrince commented Feb 1, 2018

前言

弄完了前后端分离,我们自然想打包发布项目了。
不多说,就让我们来看看吧。

开发

直接上代码:

const webpack = require('webpack')
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const webpackConfigBase = require('./webpack.config.js')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const exec = require('child_process').execSync
const pkg = require('./package.json')
// 为了抽离出两份CSS,创建两份ExtractTextPlugin
// base作为基础的css,基本不变,所以,可以抽离出来充分利用浏览器缓存
// app作为迭代的css,会经常改变
const extractBaseCSS = new ExtractTextPlugin({filename:'static/css/base.[chunkhash:8].css', allChunks: true})
const extractAppCSS = new ExtractTextPlugin({filename:'static/css/app.[chunkhash:8].css', allChunks: true})

// 减少路径书写
function resolve(dir) {
  return path.join(__dirname, dir)
}
// 网站图标配置
const favicon = resolve('favicon.ico')
// 网站版本号设置
let appVersion = ''
try {
  appVersion = exec('git rev-parse --short HEAD').toString().replace(/\n/, '')
} catch (e) {
  console.warn('Getting revision FAILED. Maybe this is not a git project.')
}

const config = Object.assign(webpackConfigBase, {
  // You should configure your server to disallow access to the Source Map file for normal users!
  devtool: 'source-map',
  entry: {
    app: resolve('app/index.js'),
    // 将第三方依赖(node_modules)的库打包,从而充分利用浏览器缓存
    vendor: Object.keys(pkg.dependencies)
  },
  output: {
    path: resolve('dist'),
    // publicPath: 'https://cdn.self.com'
    publicPath: resolve('dist/'),
    filename: 'static/js/[name].[chunkhash:8].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        include: [resolve('app')],
        loader: 'babel-loader'
      },
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        loader: 'vue-loader',
        options: {
          extractCSS: true,
          loaders: {
            scss: extractAppCSS.extract({
              fallback: 'vue-style-loader',
              use: [
                {
                  loader: 'css-loader',
                  options: {
                    sourceMap: true
                  }
                },
                {
                  loader: 'postcss-loader',
                  options: {
                    sourceMap: true
                  }
                },
                {
                  loader: 'sass-loader',
                  options: {
                    sourceMap: true
                  }
                }
              ]
            })
          }
        }
      },
      {
        test: /\.(css|scss)$/,
        use: extractBaseCSS.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              options: {
                sourceMap: true
              }
            },
            {
              loader: 'postcss-loader',
              options: {
                sourceMap: true
              }
            },
            {
              loader: 'sass-loader',
              options: {
                sourceMap: true
              }
            }
          ]
        })
      },
      {
        test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 8192,
          name: 'static/img/[name].[hash:8].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 8192,
          name: 'static/font/[name].[hash:8].[ext]'
        }
      }
    ]
  },
  plugins: [
    // Scope hosting
    new webpack.optimize.ModuleConcatenationPlugin(),
    // 删除build文件夹
    new CleanWebpackPlugin(
      resolve('dist')
    ),
    // 抽离出css
    extractBaseCSS,
    extractAppCSS,
    // 提供公共代码vendor
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'static/js/[name].[chunkhash:8].js'
    }),
    // html 模板插件
    new HtmlWebpackPlugin({
      appVersion,
      favicon,
      filename: 'index.html',
      template: resolve('app/index.html'),
      minify: {
        removeComments: true,
        collapseWhitespace: false
      }
    }),
    // 定义全局常量
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    // 可视化分析
    new BundleAnalyzerPlugin(),
    // 加署名
    new webpack.BannerPlugin('Copyright by 子咻 https://github.com/CodeLittlePrince/blog'),
  ]
})

module.exports = config

代码几乎全都有注释,有不懂的可以在评论去留言。

思考

虽然代码写好了,但是我们不禁发出一声“卧槽”,好多和webpack.config.js一样的代码啊,要是改了一样的代码部分,我还得同时改两份,而且,这么多的冗余代码对于一个优秀的程序员来讲,是不可容忍的。
那我们改怎么呢?

重构webpack的配置代码

一、创建一个基础的webpack配置文件

我们就叫webapck.config.base.js吧:

const path = require('path')

// 为了抽离出两份CSS,创建两份ExtractTextPlugin
// base作为基础的css,基本不变,所以,可以抽离出来充分利用浏览器缓存
// app作为迭代的css,会经常改变
const isProduction = process.env.NODE_ENV === 'production'
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const extractBaseCSS =
  new ExtractTextPlugin(
    {
      filename:'static/css/base.[chunkhash:8].css',
      allChunks: true,
      disable: !isProduction // 开发环境下不抽离css
    }
  )
const extractAppCSS
  = new ExtractTextPlugin(
    {
      filename:'static/css/app.[chunkhash:8].css',
      allChunks: true,
      disable: !isProduction // 开发环境下不抽离css
    }
  )

// 减少路径书写
function resolve(dir) {
  return path.join(__dirname, dir)
}

// 网站图标配置
const favicon = resolve('favicon.ico')

// __dirname: 总是返回被执行的 js 所在文件夹的绝对路径
// __filename: 总是返回被执行的 js 的绝对路径
// process.cwd(): 总是返回运行 node 命令时所在的文件夹的绝对路径
const config = {
  resolve: {
    // 扩展名,比如import 'app.vue',扩展后只需要写成import 'app'就可以了
    extensions: ['.js', '.vue', '.scss', '.css'],
    // 取路径别名,方便在业务代码中import
    alias: {
      api: resolve('app/api/'),
      common: resolve('app/common/'),
      views: resolve('app/views/'),
      components: resolve('app/components/'),
      componentsBase: resolve('app/componentsBase/'),
      directives: resolve('app/directives/'),
      filters: resolve('app/filters/'),
      mixins: resolve('app/mixins/')
    }
  },
  // loaders处理
  module: {
    rules: [
      {
        test: /\.js$/,
        include: [resolve('app')],
        loader: [
          'babel-loader',
          'eslint-loader'
        ]
      },
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        loader: 'vue-loader',
        options: {
          extractCSS: true,
          loaders: {
            scss: extractAppCSS.extract({
              fallback: 'vue-style-loader',
              use: [
                {
                  loader: 'css-loader',
                  options: {
                    sourceMap: true
                  }
                },
                {
                  loader: 'postcss-loader',
                  options: {
                    sourceMap: true
                  }
                },
                {
                  loader: 'sass-loader',
                  options: {
                    sourceMap: true
                  }
                }
              ]
            })
          }
        }
      },
      {
        test: /\.(css|scss)$/,
        use: extractBaseCSS.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              options: {
                sourceMap: true
              }
            },
            {
              loader: 'postcss-loader',
              options: {
                sourceMap: true
              }
            },
            {
              loader: 'sass-loader',
              options: {
                sourceMap: true
              }
            }
          ]
        })
      },
      {
        test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 8192,
          name: isProduction
            ? 'static/img/[name].[hash:8].[ext]'
            : 'static/img/[name].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 8192,
          name: isProduction
            ? 'static/font/[name].[hash:8].[ext]'
            : 'static/font/[name].[ext]'
        }
      }
    ]
  }
}

module.exports = {
  config,
  favicon,
  resolve,
  extractBaseCSS,
  extractAppCSS
}

二、重构webpack开发环境配置

const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const webpackConfigBase = require('./webpack.config.base.js')

const config = Object.assign(webpackConfigBase.config, {
  // sourcemap 模式
  devtool: 'cheap-module-eval-source-map',
  // 入口
  entry: {
    app: webpackConfigBase.resolve('app/index.js')
  },
  // 输出
  output: {
    path: webpackConfigBase.resolve('dev'),
    filename: 'index.bundle.js'
  },
  plugins: [
    // html 模板插件
    new HtmlWebpackPlugin({
      favicon: webpackConfigBase.favicon,
      filename: 'index.html',
      template: webpackConfigBase.resolve('app/index.html')
    }),
    // 抽离出css,开发环境其实不抽离,但是为了配合extract-text-webpack-plugin插件,需要做个样子
    webpackConfigBase.extractAppCSS,
    webpackConfigBase.extractBaseCSS,
    // 热替换插件
    new webpack.HotModuleReplacementPlugin(),
    // 更友好地输出错误信息
    new FriendlyErrorsPlugin()
  ],
  devServer: {
    proxy: {
      // 凡是 `/api` 开头的 http 请求,都会被代理到 localhost:7777 上,由 koa 提供 mock 数据。
      // koa 代码在 ./mock 目录中,启动命令为 npm run mock。
      '/api': {
        target: 'http://localhost:7777', // 如果说联调了,将地址换成后端环境的地址就哦了
        secure: false
      }
    },
    host: '0.0.0.0',
    port: '9999',
    disableHostCheck: true, // 为了手机可以访问
    contentBase: webpackConfigBase.resolve('dev'), // 本地服务器所加载的页面所在的目录
    // historyApiFallback: true, // 为了SPA应用服务
    inline: true, //实时刷新
    hot: true  // 使用热加载插件 HotModuleReplacementPlugin
  }
})

module.exports = config

三、重构webpack开发环境配置

const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const exec = require('child_process').execSync
const webpackConfigBase = require('./webpack.config.base.js')
const pkg = require('./package.json')

// 网站版本号设置
let appVersion = ''
try {
  appVersion = exec('git rev-parse --short HEAD').toString().replace(/\n/, '')
} catch (e) {
  console.warn('Getting revision FAILED. Maybe this is not a git project.')
}

const config = Object.assign(webpackConfigBase.config, {
  // You should configure your server to disallow access to the Source Map file for normal users!
  devtool: 'source-map',
  entry: {
    app: webpackConfigBase.resolve('app/index.js'),
    // 将第三方依赖(node_modules)的库打包,从而充分利用浏览器缓存
    vendor: Object.keys(pkg.dependencies)
  },
  output: {
    path: webpackConfigBase.resolve('dist'),
    // publicPath: 'https://cdn.self.com'
    publicPath: webpackConfigBase.resolve('dist/'),
    filename: 'static/js/[name].[chunkhash:8].js'
  },
  plugins: [
    // Scope hosting
    new webpack.optimize.ModuleConcatenationPlugin(),
    // 删除build文件夹
    new CleanWebpackPlugin(
      webpackConfigBase.resolve('dist')
    ),
    // 抽离出css
    webpackConfigBase.extractAppCSS,
    webpackConfigBase.extractBaseCSS,
    // 提取公共代码vendor
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'static/js/[name].[chunkhash:8].js'
    }),
    // html 模板插件
    new HtmlWebpackPlugin({
      appVersion,
      favicon: webpackConfigBase.favicon,
      filename: 'index.html',
      template: webpackConfigBase.resolve('app/index.html'),
      minify: {
        removeComments: true,
        collapseWhitespace: false
      }
    }),
    // 定义全局常量
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    // 可视化分析
    new BundleAnalyzerPlugin(),
    // 加署名
    new webpack.BannerPlugin('Copyright by 子咻 https://github.com/CodeLittlePrince/blog'),
  ]
})

module.exports = config

代码瞬间变得清晰、精简、高大上有没有?!(^-^)V

看一下打包处理后代码情况(兼容IE10及以上):

总结

这一篇我们编写了开发环境用的webpack配置文件,然后发现代码的冗余从而重构了开发和发布环境的webpack配置。

之后,我们还需要能够自动测试我们写的业务代码,避免人工手动各种戳页面(虽然大部分公司都是这么干的,即使是大公司会腾出时间和人手写测试用例的部门也不多),不过架构还是要做的。

下篇我们会来完成测试的流程 - 从零开始做Vue前端架构(6)

项目完整代码

Vue前端架构-by 子咻

@CodeLittlePrince CodeLittlePrince changed the title 从零开始做Vue前端架构(5) 从零开始做Vue前端架构(5)webpack发布环境配置 & 配置重构 Apr 22, 2018
@CodeLittlePrince CodeLittlePrince changed the title 从零开始做Vue前端架构(5)webpack发布环境配置 & 配置重构 从零开始做Vue前端架构(5)webpack发布版配置 & 配置重构 Apr 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant