阅读 412

webpack 构建前端项目(2)

上一篇讲了一些webpack基础的配置,从这节开始正式构建项目,拿来做演示的是一个移动端项目,淘宝上买的设计图,计划做成一个多模块,模块单独建立和维护,模块间的升级和改造互不影响,但保持项目代码风格、UI风格的统一。

配置文件拆分

根据生产环境和开发环境的差异,需要将webpack的配置文件分成基础配置文件、开发配置文件和生产配置文件。删除 webpack.config.js文件 新建build目录,在下面建立base.conf.js、dev.conf.js、prod.conf.js,param.js,pretreat.js、build.js、dev.js。

在src目录下新建modules文件夹作为各个模块的目录文件夹,新建一个src/modules/home文件夹作为home模块,入口文件为:src/modules/home/main.js

命令行参数配置文件

新建一个param.js文件,里面用存放命令行参数、NODE_ENV;用到了minimist。

npm i minimist -D复制代码

const param=require('minimist')(process.argv.slice(2))
module.exports={
  mock:param.mock?true:false, 
  moduleName:param.module==true?'':param.module,
  isDev:process.env.NODE_ENV==='development',
  openB:param.open || false

}复制代码

在package.json script中配置命令

"dev:mock": "set NODE_ENV='development' && node build/dev.js --mock --module"复制代码

然后运行 npm run dev:mock home;  minimist就会接受到 {mock:true, module:home} 这样的参数。

配置预处理文件

新建一个pretreat.js文件,根据命令行参数生成 webpack配置文件

const {moduleName,isDev}=require('./param')
const copyPlugin=require('copy-webpack-plugin')
const fs=require('fs')
const path=require('path')
const chalk=require('chalk')
const res=p=>path.join(process.cwd(),p)
let Err={msg:''} 
Object.defineProperty(Err,'msg',{
  set(val){
    if(val!==''){ //异常处理
      console.log(chalk.red(val+'\n'))
      process.exit(1)
    }
  }})
const pretreatConfig=()=>{
  Err.msg=moduleName?'':'please input a module name' //判断是否输入模块名
  const exit=fs.existsSync(res(`src/modules/${moduleName}`)) //判断模块是否存在
  Err.msg=exit?'':`module ${moduleName} not found`
   let entry={}
  entry[moduleName]=res(`src/modules/${moduleName}/main.js`)  //入口文件
  return{
    entry,
    output:{
      filename:'js/index.js',
      path:res(`dist/${moduleName}`), 
     publicPath:'/'
    },
    plugins:[
      new copyPlugin([
        {
          from:res( 'public'), 
         to: res(`dist/${moduleName}`),
          ignore: ['.*']
        }
      ])
    ]
  }
}
module.exports=pretreatConfig()复制代码

用到了copy-webpack-plugin 可以把制定目录的文件复制进打包输出的目录

npm i copy-webpack-plugin -D复制代码

根据打包的模块名配置了入口文件 出口文件 以及需要复制的文件输出的位置。

基础配置文件base.conf.js

基础配置配置了开发和生产通用的配置文件如下:

const {moduleName}=require('./param')
const vuePlugin=require('vue-loader/lib/plugin')
const htmlPlugin=require('html-webpack-plugin')
const merge=require('webpack-merge')
const pretreat=require('./pretreat')
const path=require('path')
const res=p=>path.join(process.cwd(),p)
module.exports=merge(pretreat,{
  module:{
    rules:[
      {
        test:/\.(jpe?g|gif|png|svg)(\?.*)?$/,
        use:[
          {
            loader:'url-loader',
            options:{
              limit:10000,
              filename:'[name].[hash:5].[ext]',
              outputPath:'imgs/'
            }
          }
        ]
      },
      {
        test:/\.js$/,
        use:[
          'babel-loader?cacheDirectory',
          {
            loader:'eslint-loader',
            options:{
              fix:true
            }
          }
        ]
      },
      {
        test:/\.vue$/,
        use:'vue-loader'
      }
    ]
  },
  plugins:[
    new vuePlugin(),
   new htmlPlugin({
      template:res('template/template.html'),
      title:'',
      filename:`${moduleName}.html`,
      minify:{
        collapseWhitespace:true,
      }
    })
  ],
  resolve:{
    extensions:['.js','.vue','.json'],
    alias:{  //别名设置
      '@':res('src'),
      '@style':res('src/common/style'),
      '@js':res('src/common/js'),
      '@mock':res('mock'),
      '@component':res('src/component')
    }
  },
  externals:{
  }
})复制代码

新建一个tempalte/tempalte.html 文件 作为html-wenbpack-plugin的模板文件

eslint

eslint,用于自动检测代码风格及模块引用错误,参考了小诺哥的做法,在命令行输入

npm i eslint eslint-loader babel-eslint standard -D复制代码

在根目录增加一个  .eslintrc.js文件

module.exports = {
  "parser": "babel-eslint",
  "parserOptions": {
    "ecmaVersion": 6
  },
  "env": {
    "es6": true,
    "browser": true,
    "node": true,
    "commonjs": false,
    "mocha": true,
    "jquery": true,
  },  
"globals": { //全局变量检测
    "globalVar1": true,
    "globalVar2": false,
    "IS_DEV":false,
    "MOCK":false,
    "App":true
  },
  "rules": {
    "camelcase":0, //关闭驼峰式写法检测
     "eqeqeq": "warn",
    "curly": 2,
    "quotes": ["error", "double"],
    "one-var": ["error", {
      "var": "always",
      "let": "never",
      "const": "never"
    }],
  }, 
 "settings": {
    "sharedData": "Hello"
  },
  "root": true,
  "extends": [
    "standard"
  ]
}复制代码

增加一个 .eslintignore文件

/dist/
/node_modules/
/*.js
复制代码

生产配置文件prod.conf.js

设置mode:'production'

const conf=require('./base.conf')
const {CleanWebpackPlugin}=require('clean-webpack-plugin')
const merge=require('webpack-merge')
const miniCss=require('mini-css-extract-plugin')
const terser=require('terser-webpack-plugin')
const optimizeCss=require('optimize-css-assets-webpack-plugin')
const webpack=require('webpack')
const uglifyJs=require('uglifyjs-webpack-plugin')
const path=require('path')
const res=p=>path.join(process.cwd(),p)
module.exports=merge(conf,{
  mode:'production',
  module:{
    rules:[
      {
        test:/\.(le|c)ss$/,
        use:[
          miniCss.loader,
          'css-loader',
          {
            loader:'postcss-loader',
            options:{
              ident:'postcss'
            }
          },
          'less-loader',
          {
            loader:'style-resources-loader',
            options:{
              preProcessor:'less',
              patterns:[res('src/common/style/fn.less')]
            }
          }
        ]
      }
    ]
  },
  plugins:[
    new CleanWebpackPlugin(),
    new miniCss({
      filename:'css/[name].[hash:5].css',
      chunkFilename:'css/[id].[hash:5].css'
    }),
    new webpack.DefinePlugin({  //定义项目全局变量
      MOCK:false,
        IS_DEV:false
    })
  ],
  optimization:{
    minimizer:[
      new terser({
        parallel:true,
        cache:true,
      }),
      new optimizeCss({}),
      new uglifyJs({
        exclude:/\/mock/,  //忽略 mock文件夹的打包
        uglifyOptions:{
          compress:{
            drop_debugger:true,  
            drop_console:true   //清理console.log
          }
        }
      })
    ],
    splitChunks: { //代码拆分
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 1,
      automaticNameDelimiter: '~', 
     automaticNameMaxLength: 30,
      name: true,
      cacheGroups: {
        vendors: {  
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name:'vendor'
        }
      }
    }
  }
})复制代码

npm i webpack-merge mini-css-extract-plugin terser-webpack-plugin optimize-css-saaets-plugin uglifyJs-webpack-plugin -D 复制代码
  • webpack-merge 对webpack配置文件进行合并
  • mini-css-extract-plugin:将style中的样式提取出来作为单独的文件。
  • webpack.DefinePlugin 定义全局变量,在项目中可以直接使用
  • optimization.splitChunks 对代码进行拆分,
  • terser-webpack-plugin 对js代码进行压缩
  • optimize-css-assets-plugin 对css代码进行压缩
  • uglifyJs-webpack-plugin 对js进行优化 自动清理项目中的console.log等打印信息


使用postcss自动添加前缀,转px为rem

命令行安装

npm i postcss-loader postcss-import autoprefixer postcss-pxtorem -D复制代码

在根目录新建一个.postcssrc.js文件,rootValue 项目里的100px 将转换成 1rem

module.exports = {
  plugins:{
    'autoprefixer':{},
    'postcss-pxtorem':{
      rootValue:100,
          propList:['*']
    }
  }}复制代码

template/template.html 后面加上以下代码

<script>
(function (doc, win) {
  var docEl = doc.documentElement
  var resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'
  function recalc() {
    var designWidth = 750  //设计稿宽度
    var clientWidth = docEl.clientWidth
    if (!clientWidth) return
    if(clientWidth > designWidth){ //限定缩放最大比例 750px
      docEl.style.fontSize='100px'  
    }else{
      docEl.style.fontSize = (100 * clientWidth / designWidth) + 'px'   //设置html字体大小
    }
  }
  if (!doc.addEventListener) return
  win.addEventListener(resizeEvt, recalc, false)
  doc.addEventListener('DOMContentLoaded', recalc, false)})
(document, window)
</script>复制代码

designWidth 为设计稿的宽度

在packge.json里面加入

  "browserslist": [
    "> 0.2%",
    "last 3 versions"
  ]复制代码

全局引用less文件

将一些less共公的参数、变量、mixin提取出来作为一个单独的文件,自动注入到每一个.vue中,用到了style-resources-loader

npm i style-resources-loader复制代码

      {
        test:/\.(le|c)ss$/,
        use:[
          miniCss.loader,
          'css-loader',
          { 
           loader:'postcss-loader',
            options:{
              ident:'postcss'
            }
          },
          'less-loader',
          {
            loader:'style-resources-loader',
            options:{
              preProcessor:'less',
              patterns:[res('src/common/style/fn.less')]  //less文件路径
            }
          }
        ]
      }复制代码

新建build.js

const webpack=require('webpack')
const conf=require('./prod.conf')
webpack(conf,(err,status)=>{
  if(err){    throw err  }
  process.stdout.write(str.toString({
    colors:true,
    modules:false,
    children:false,
    chunks:false,
    chunkModules:false
  }))
})复制代码

使用webpack()打包项目,package.json的script添加:

"build": "set NODE_ENV='production' && node build/build.js --module",复制代码

命令行输入npm run build home 即可看见打包后的文件


开发配置文件dev.conf.js

设置 mode:'development'

const merge=require('webpack-merge')
const conf=require('./base.conf')
const vConsole=require('vconsole-webpack-plugin')
const webpack=require('webpack')
const {mock}=require('./param')
module.exports=merge(conf,{
  mode:'development',
  devtool:'inline-sourceMap',
  module:{
    rules:[
      {
       test:/\.(le|c)ss$/, 
       use:[
          'style-loader',
          {
            loader:'css-loader',
            options:{
              sourceMap:true,
            }
          },
          {
            loader:'postcss-loader',
            options:{
              ident:'postcss',
              sourceMap:true,
            }
          },
          {
            loader:'less-loader',
            options:{
              sourceMap:true
            }
          },
          {
            loader:'style-resources-loader',
            options:{
              preProcessor:'less',
              patterns:[res('src/common/style/fn.less')]
            }
          }
        ]
      }
    ]
  },
  plugins:[
    new vConsole({
      enable:true
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({
      MOCK:mock,
      IS_DEV:true
    })
  ]})复制代码

设置devtool为’inline-sourceMap'开启js文件跟踪,

css-loader postcss-loader less-loader 设置 options 的sourceMap为true 跟踪样式文件。

安装 vconsole-webpack-plugin ,用于在页面调起控制台,适合手机调试

npm i vconsole-webpack-plugin复制代码

开启服务 dev服务

在config目录下新建一个local文件,里面写入模块的端口号,

module.exports = {
  router: {
    login: 8080,
    home: 8081,
    car: 8082
  }}复制代码

在dev.js中写入

const conf=require('./dev.conf')
const devMiddleware=require('webpack-dev-middleware')
const hotMiddleware=require('webpack-hot-middleware')
const express=require('express')
const app=new express()
const webpack=require('webpack')
const open=require('open')
const {mock,moduleName,openB}=require('./param')
const port=require('../config/local.js').router
const path=require('path')
const res=p=>path.join(process.cwd(),p)
const compiler=webpack(conf)
app.use(devMiddleware(compiler,{
  publicPath:conf.output.publicPath,
  quiet:true
}))
app.use(hotMiddleware(compiler,{
  log:false,
  heartbeat:1000
}))
if(mock){
  app.use(express.static(res('mock/assets')))
}
app.listen(port[moduleName],()=>{
  console.log(`server running at http://localhost:${port[moduleName]}`)
  if(openB){
    open(`http://localhost:${port[moduleName]}/${moduleName}.html/#/`)
  }})复制代码

用到了webpack-dev-middleware webpack-hot-middleware express open

npm i webpack-dev-middleware webpack-hot-middle-ware express open -D复制代码

  •  express 构建本地服务器
  • webpack-dev-middleware:webpack服务中间件 
  • webpack-dev-middleware:webpack热服务中间件,项目文件更改时,不刷新页面,自动替换更改的内容,需在pretreat.js 将entry设置成:
  • entry[moduleName]=isDev?['webpack-hot-middleware/client?noInfo=true&reload=true',res(`src/modules/${moduleName}/main.js`)]:res(`src/modules/${moduleName}/main.js`)复制代码

  • 在dev.conf.js中加入插件
  • new webpack.HotModuleReplacementPlugin()复制代码

  • open:在浏览器中打开网址

在pakage.json scripts中添加

"dev": "set NODE_ENV='development' && node build/dev.js --open --module"复制代码

在命令行输入 npm run dev home 即可开启dev服务