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

基于 webpack 的多页应用框架优化 #6

Open
jinlong opened this issue Sep 5, 2018 · 0 comments
Open

基于 webpack 的多页应用框架优化 #6

jinlong opened this issue Sep 5, 2018 · 0 comments
Labels

Comments

@jinlong
Copy link
Owner

jinlong commented Sep 5, 2018

mobile-intl 框架起源

基于多页框架项目 vue-multiple-pages 优化

基础 webpack 配置

配置文件

  1. 通常情况,根目录下面的 webpack.config.js 文件作为默认配置文件

  2. 自定义配置文件,使用 --config 指定配置文件

package.json

"scripts": {
  "build": "webpack --config prod.config.js"
}

常用配置项:

const path = require("path");

module.exports = {
  // 选择模式,webpack 可以根据选择启用内置优化
  mode: "production", // "production" | "development" | "none"

  // webpack 打包入口,单页应用配字符串,多页应用配对象或数组
  entry: "./app/entry", // string | object | array
  entry: {
    a: "./app/entry-a",
    b: ["./app/entry-b1", "./app/entry-b2"]
  },

  // 打包文件输出配置
  output: {
    // 所有文件的输出目录,必须是个绝对路径
    path: path.resolve(__dirname, "dist"), // string
    filename: "bundle.js", // string
    filename: "[name].js", // 多入口
    // 分块(chunks)入口的名字
    publicPath: "/mobile-intl/" // string
    // 输出路径相对于 HTML 页面的 url
  },

  // 处理各种模块的 Loader
  module: {
    rules: [{
        test: /\.jsx?$/,
        include: [path.resolve(__dirname, "app")],
        exclude: [path.resolve(__dirname, "app/demo-files")],
        loader: "babel-loader", // Rule.use: [ { loader } ] 的简写,loader 名称
        options: {
          // Rule.use: [ { options } ] 的简写,loader 配置
          presets: ["es2015"]
        }
      },
      {
        test: /\.html$/,
        use: [
          // 配置多个 loader
          "htmllint-loader",
          {
            loader: "html-loader",
            options: {
              /* ... */
            }
          }
        ]
      }
    ]
  },

  // 模块解析配置
  resolve: {
    // 模块搜索目录
    modules: ["node_modules", path.resolve(__dirname, "app")],
    // 文件扩展名
    extensions: [".js", ".json", ".jsx", ".css"],
    // 别名
    alias: {
      vue: "vue/dist/vue.js",
      vuex: "vuex/dist/vuex.js",
      "@": path.resolve("src")
    }
  },

  // 性能报告
  performance: {
    hints: "warning", // enum
  },
  // source map 如何生成,调试便利和编译速度如何权衡
  devtool: "source-map",
  // 配置 webpack-serve
  serve: {
    port: 1337,
    content: "./dist"
  },
  // 精确控制命令行输出日志
  stats: "errors-only",

  // webpack-dev-server 配置
  devServer: {
    proxy: {
      // 后端接口代理,可用于 mock 数据
      "/api": "http://localhost:3000"
    },
    contentBase: path.join(__dirname, "public"), // boolean | string | array, 静态文件目录
    compress: true, // 启用 gzip 压缩
    hot: true, // 模块热替换
    noInfo: true, // 热更新时仅输出错误和警告日志
    stats: { // 命令行日志信息
    },
  },

  // 插件配置
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin(),
  ]
};

全部配置见官方文档

快速生成自定义配置文件,可以用官方推荐工具

打包输出目录优化

精简多余的目录

// CSS 目录
const extractCSS = new ExtractTextPlugin({
  // filename: 'assets/css/[name].css',
  filename:  (getPath) => {
    return getPath('assets/css/[name].css').replace('views/', '');
  }
})

// HTML 目录
filename = filename.replace('views/', '')
const htmlConf = {
  filename: filename,
  template: path.replace(/.js/g, '.html'),
  inject: 'body',
  hash: true,
  chunks: ['commons', chunk]
}

// JS 目录
const _chunk = path.split('./src/pages/')[1].split('/app.js')[0]
const chunk = _chunk.replace('views/', '')

项目启动优化

From

npm start

To

# 自定义调试项目,customer 为项目的目录名字
npm start -- --index=customer

实现思路

想办法把 openPage 字段变成动态值

devServer: {
  open: true,
  openPage: 'mobile-intl/index.html',
}

如何实现

  1. 传入参数
npm start -- --index=customer
// package.json
scripts: {
  "start": "node ./tools/runDev.js",
}
  1. Node.js 截取参数
let params = process.argv
  1. 补全路径
let indexPage = params.find(function(it) {
  return it.indexOf('--index=') > -1  // 找到起始页标识
})
...
let indexPath = indexPage.split('--index=')[1]
indexPage = 'mobile-intl/'+ indexPath +'/index.html'
  1. 设置 webpack 参数
let shell = require('shelljs');

let script = 'webpack-dev-server --open-page='+ indexPage +' --inline --hot --config build/webpack.dev.conf.js'
shell.exec(script)

添加路径别名

alias: {
  '@': resolve('src'),
  'pages': resolve('src/pages'),
  'components': resolve('src/components'),
  'services': resolve('src/services')
}
import { gtagPV } from '@/utils/gtag';

不同环境自动适配域名

原则上不允许硬编码域名,直接写路径 path,或者引入以下 JS 统一处理

// 前端URL,直接写路径
window.location.href = '/helloworld/help/index.html'

// 后端接口
import conf from 'services/config'
console.log(conf.host)

webpack 在打包时会自动对域名相关信息进行替换。

如何实现

  1. 针对不同环境,使用不同的配置文件,比如:config.js,config-qa.js

保证所有配置文件导出的参数名保持一致

// dev 测试环境配置
let baseUrl = 'dev.xxx.com'  // 服务端接口环境
let host = '//'+ window.location.hostname  // 前端网页域名

export default {
  baseUrl,
  host
}
  1. webpack 通过 NormalModuleReplacementPlugin 插件完成替换
const prodWebpackConfig = merge(webpackConfig, {
    plugins: [
      new webpack.NormalModuleReplacementPlugin(
        /services\/config\.js/, // services 目录默认配置文件
        './config-qa.js'  // 替换配置文件
      )
    ]
  })

接口请求完善

  1. 编写通用拦截器
// 请求拦截器
axios.interceptors.request.use(function (config) {
  // 自动添加 token 之类的通用参数
  return config;
}, function (error) {
  return Promise.reject(error);
});

// 响应拦截器
axios.interceptors.response.use(function (response) {
  // 处理一些通用响应,比如 200,401之类
  return response;
}, function (error) {
  return Promise.reject(error);
});

使用时引入一次拦截器,发起请求同 axios API

import axios from 'services/axios'
axios.get('/api?param=value')
.then(function (response) {
    console.log(response);
})
.catch(function (error) {
    console.log(error);
});
  1. 请求方法封装
import { postFromData } from 'services/axios'
// postFromData:post 请求以 form 表单形式发送数据,默认为 JSON
postFromData.then((response) => {
    console.log(response);
}).catch((error) => {
    console.log(error);
})

服务器部署优化

优化效果

mobile-intl-deploy

# 单独打包
npm run buildDev        // 打 dev 环境包
npm run buildQa         // 打 QA 环境包
npm run buildPre        // 打仿真环境包
npm run buildOnline     // 打线上环境包

# 单独部署
npm run dev         // 部署 dev 测试环境
npm run qa          // 部署 QA 测试环境
npm run pre         // 部署仿真测试环境
npm run online      // 部署线上环境

# 打包 + 部署快捷方式
# 第一个参数为打包所用的 server 接口环境,第二个参数为服务器部署环境
# 两个参数的可选值均为:dev || qa || pre || online
# 调用方式一
npm run deploy -- --buildEnv=dev --deployEnv=qa
# 调用方式二
npm run deploy -- dev qa

如何实现

  1. 创建通用的 webpack 配置:webpack.cust.conf.js,处理各个环境的打包逻辑
// exports function 能接收参数
module.exports = function(env, argv){
  // 根据 npm run script 传入的参数判断环境
  let apiEnv = env.serverApi || 'dev'
  if (apiEnv === 'dev'){
    // 做点啥
  }
}
  1. npm script 传入 webpack 参数:serverApi,实现环境自定义
"scripts": {
  "buildDev": "webpack --env.serverApi dev --mode production --progress --color --config build/webpack.cust.conf.js",
  "buildQa": "webpack --env.serverApi qa --mode production --progress --color --config build/webpack.cust.conf.js",
}
  1. npm script 传入 node 参数:env,实现部署服务器自定义
"scripts": {
  "dev": "node deploy.js --env=dev --color=always",
  "qa": "node deploy.js --env=qa --color=always",
}
// deploy.js 为部署脚本
let envParam = process.argv[2].slice(6)  // 裁掉 --env= 获取环境参数
let deployEnv = envParam || 'dev' // 服务器环境

初始化项目脚手架

初始化项目结构

  • 单页项目模板 /src/pages/singlePage
  • 多页项目模板 /src/pages/multiPage

根据提问完成项目初始化

npm run initApp

如何实现

并没用Yeoman

关键工具

提问交互

const askQuestion = async () => {
  // 模板类型
  let { templateType } = await inquirer.prompt([
    {
      type: 'list',
      name: 'templateType',
      message: 'Which template do you want?',
      choices: [
        {
          name: 'SPA (Single page)',
          value: 'SPA'
        }
      ]
    }
  ])

  // 项目名称
  let { projectName } = await inquirer.prompt([
    {
      name: 'projectName',
      message: 'What\'s the project name',
    }
  ])
}

askQuestion().catch((err) => {
  console.log('init project err: ', err)
})

拷贝工程模板

let shell = require('shelljs');
let sourcePath = '../src/pages/singlePage'
shell.cp('-R', sourcePath, '../src/pages/newProject');

打包时排除模板目录

glob.sync('./src/pages/**/app.js', {
  // 忽略项目模板目录
  ignore: ['./src/pages/singlePage/**', './src/pages/multiPage/**']
}).forEach(path => {
  // do something
})

独立脚手架模板

可以利用 download-git-repo,把项目模板从 github 下载下来

命令行日志美化

关键工具

遇到的坑

使用 shelljs 库执行的时候,命令行的样式失效,issue 在此

解决办法

强制启用命令行样式

  • webpack 加 --color
webpack --env.serverApi dev --mode production --progress --color --config build/webpack.cust.conf.js
  • Node 加 --color=always
cd ./tools && node deploy.js --env=dev --color=always

自定义日志输出样式

参考了 signale 的样式,直接用它也没问题

const chalk = require('chalk');
// log with chalk
const chalkLog = function(options){
  options = options || {
    color: 'red',  // chalk color
    label: 'my-project',
    badge: '🎉',
    tag: 'tag',
    msg: 'some log msg',
  }
  let logLabel = options.label || 'my-project'
  let logColor = options.color || 'black'
  const logTxt = chalk.gray('['+ logLabel +'] > ') + chalk[logColor](''+ options.badge +' ') + chalk.underline(chalk[logColor](options.tag)) +'  '+ options.msg

  console.log(logTxt)
}
@jinlong jinlong added the article label Sep 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant