webpack 構建前端項目(3)

上篇介紹了打包和構建dev服務相關的內容,這篇介紹src相關的構造。html

src文件目錄結構


  • src/modules存放各個模塊;
  • src/store存放公共的vuex;
  •  common文件夾存放公共的文件;
  • component文件夾存放組件文件。

模塊內部 以home爲例,vue

  • home/main.js是入口文件,
  • home/router下爲vue-router文件
  •  home/store爲vuex文件,
  • views爲頁面文件夾

命令行新建/刪除文件

採用命令行新建和刪除文件,能夠保證爲保證各個模塊之間、各個文件之間的目錄結構風格的統一,方便項目的修改與維護。node

在template文件夾下新建init文件夾、router文件夾、views文件夾,分別做爲模塊初始化、router添加、頁面添加的模板文件。webpack

init文件夾下有 main.js,app.vue,router文件夾 store文件夾做爲模塊的初始文件。git


安裝 copy-template-dir 用於複製文件,安裝 rimraf用於刪除文件,安裝inquirer用於獲取用戶命令行輸入。github

npm i copy-template-dir rimraf inquirer -D複製代碼

在build文件夾下新建一個init-page.js文件web

建立時,若是模塊不存在,測將 template下的init文件夾複製進模塊文件夾,而後將tempalte下views文件夾複製進創建的模塊下的views文件夾下vue-router


const chalk=require('chalk')
const fs=require('fs')
const copy=require('copy-template-dir')
const rm=require('rimraf')
const pathParam=require('minimist')(process.argv.slice(2))
const args=pathParam.path  //傳入的參數 如 home/index
const isDelete=pathParam.d==true  //是不是 delete-page命令
const {prompt}=require('inquirer')
const path=require('path')
const res=p=>path.join(process.cwd(),p)

//異常處理
let err={msg:''}
Object.defineProperty(err,"msg",{
  set(val){
    if(val){
      console.log(chalk.red(val))
      process.exit(1)
    }
  }})

err.msg=args?'':'please input a router'  //驗證輸入非空
err.msg=/^((\w|-)+)(\/)((\w|-|\/)+)?((\w|-)+)$/.test(args)?'':'input a useful param, not include "*、?、&" and so on' //特殊字符驗證
const mod=args.split('/')[0]  //模塊名
const pat=args.split('/').slice(1).join('/')  //路徑

fs.stat(res(`src/modules/${mod}/router/${pat}`),(er)=>{ //判斷文件是否已存在
  if(!er&&!isDelete){
      err.msg='file already existed,no create again '
  }else if(er&&!isDelete){
    createProject()
  }else if(isDelete){
    deleteProject()
  }})

const createProject=()=>{  //新建
  const exist=fs.existsSync(res(`src/modules/${mod}`))
    if(!exist){
      copy(res('template/init'),res(`src/modules/${mod}`),er=>{ //建立模塊
        err.msg=er?'fail to create module,please checkout':''
        console.log('copy module')
        processProject()  //建立內容
      })
    }else{
      processProject() //建立內容
    }}

const processProject=()=>{
  prompt([ //查詢用戶輸入
    {
      type:'input',
      message:'please input a title',
      name:'title'
    }
  ]).then(r=>{
      console.log(r)
    copy(res('template/views'),res(`src/modules/${mod}/views/${pat}`),er=>{ //複製頁面文件.vue
      err.msg=er?'fail to copy views,please checkout':''
      console.log('copy views')
    })
    copy(res('template/router'),res(`src/modules/${mod}/router/${pat}`),{path:pat,module:mod,title:r.title},er=>{  //複製路由文件
      err.msg=er?'fail to copy router,please checkout':''
            console.log('copy router')
    })
    const str= fs.readFileSync(res(`src/modules/${mod}/router/index.js`)) //讀取路由文件
    let str2=str.toString()
    let patName=pat.replace(/\//g,'_') //將路由'/'轉成'_'
    str2=str2.replace(/(Vue.use\(router\))/,`$1\nimport ${patName} from"\.\/${pat}/index.js"`).replace(/(routes:\[)/,`$1\n    \.\.\.${patName},`) //替換文件
    fs.writeFileSync(res(`src/modules/${mod}/router/index.js`),str2)  //寫入文件
  })
}

const deleteProject=()=>{  //刪除
  let patName=pat.replace(/\//g,'_')
  rm(res(`src/modules/${mod}/views/${pat}`),err=>{ //刪除頁面文件
    console.log('delete view')
  })
  rm(res(`src/modules/${mod}/router/${pat}`),err=>{ //刪除 路由文件
    console.log('delete router')
  })
  const str= fs.readFileSync(res(`src/modules/${mod}/router/index.js`)) //讀取主路由文件
  let str2=str.toString()
  let reg1=new RegExp(`\nimport ${patName} from"\.\/${pat}\/index\.js"`)
  let reg2=new RegExp(`\n    \.\.\.${patName},`)
  str2=str2.replace(reg1,'').replace(reg2,'') //修改主路由文件
  console.log(str2)
  fs.writeFileSync(res(`src/modules/${mod}/router/index.js`),str2) //寫入主路由文件
}複製代碼

在package.json scripts中加入vuex

"init-page": "node build/init-page --path",
"delete-page": "node build/init-page -d --path",複製代碼

在命令行輸入 "npm run init-page 模塊名/路徑" 、"npm run delete-page 模塊名/路徑"便可新增、刪除文件。express

init-page

建立時,以  "npm run init-page home/detail" 命令爲例,

  • 先進行輸入驗證,經過後
  • 若是home模塊不存在,則將 template下的init文件夾複製進 "src/modules/home" 文件夾;
  • 命令行查詢 輸入頁面標題;
  • 將tempalte下views文件夾複製進 "src/modules/home/views/detail" 文件夾下;
  • 再將template下router文件夾複製進  "src/modules/router/detail" 文件之下,並經過copy-template-dir 注入頁面標題、路徑及組件路徑;

copy(res('template/router'),res(`src/modules/${mod}/router/${pat}`),{path:pat,module:mod,title:r.title},er=>{  //複製路由文件
      err.msg=er?'fail to copy router,please checkout':''
            console.log('copy router')
    })複製代碼

//template/router/index.js文件:

export default [{
  path:'/{{path}}',
  component:()=>import('@/modules/{{module}}/views/{{path}}/index.vue'),
  meta:{
    title:'{{title}}'
  }
}]複製代碼

  • 更新 "src/modules/home/router/index.js" 文件;

delete-page

刪除時 以 「npm run delete-page home/detail」命令爲例,先進行輸入驗證,經過後

  • 刪除 "src/modules/home/views/detail"文件夾
  • 刪除 「 src/modules/home/router/detail」文件夾
  • 更新 「src/modules/home/router/index.js」文件


自動註冊基礎組件

項目的基礎組件放置 src/component/base文件夾下

在src/common/js文件夾下新建一個registerComponent.js 文件,用於自動註冊基礎組件

import Vue from "vue"
const files = require.context("@component/base", true, /index\.vue$/) //掃描基礎組件目錄
files.keys().map(files).map(item => { //生成 require對象數組
  const name = item.default.__file.replace(/^(.+\/)((\w|-|_)+)(\/index.vue)$/, "$2").replace(/(\w)/,(v)=>v.toUpperCase()) //獲取組件名字並大寫首字母
  Vue.component(`v${name}`, item.default) //註冊組件
})複製代碼

開啓mock服務

在package.json scripts中加入

"dev:mock": "set NODE_ENV='development' && node build/dev.js --open --mock --module",複製代碼

在param.js中加入

const param=require('minimist')(process.argv.slice(2))複製代碼

mock:param.mock?true:false複製代碼

在prod.conf.js文件中加入 

new webpack.DefinePlugin({
      MOCK:mock,
      IS_DEV:true
})複製代碼

在項目根目錄新建mock文件夾做爲mock目錄,

項目的請求方法中直接用require獲取mock數據

request (o) {
    const _this = this.vm
    return new Promise((resolve, reject) => {
      if (MOCK) { //判斷是否啓用mock
        try {
          const data = require(`@mock/${o.url.replace(/\//g, "_")}.js`) //獲取mock數據          _this.$load.open()          setTimeout(() => {            _this.$load.hide()            resolve(data)          }, 500)        } catch (err) {          reject({            errCode: 1,            msg: "404 wrong request"          })        }      } else {
        ....
      }catach(err){}
    })
  },複製代碼

在項目生產打包時,並不須要將mock文件打包,需在build/prod.conf.js中設置 uglifyis-webpack-plugin

const uglifyJs=require('uglifyjs-webpack-plugin')複製代碼

optimization:{
    minimizer:[
      new uglifyJs({
        exclude:/\/mock/,  //忽略mock文件夾的打包
        uglifyOptions:{
          compress:{
            drop_debugger:true,
            drop_console:true
          }
        }
      })
    ],
}複製代碼



在 build/dev.js 中 啓用 mock靜態資源服務器

const {mock,moduleName,openB}=require('./param')

const res=p=>path.join(process.cwd(),p)複製代碼

if(mock){
  app.use(express.static(res('mock/assets')))
}複製代碼


多模塊啓動

應用node.js子進程進行多個模塊的啓動和打包

新建build/server.js文件

const {moduleName,mock}=require('./param')
const {exec}=require('child_process')
const port=require('../config/local.js').router  //本地存放的端口號
const chalk=require('chalk')

//錯誤處理
let err={msg:''}
Object.defineProperty(err,'msg',{
  set(val){
    if(val!==''){
      console.log(chalk.red(val))
      process.exit()
    }
  }})

err.msg=moduleName?'':'please input a module name'let line=mock?"set NODE_ENV=development && node build/dev.js --mock --module":"set NODE_ENV=development && node build/dev.js --module"//本地配置的模塊名
let modules=Object.keys(port).reduce((ret,item)=>{  //生成配租文件 端口數組
  ret.push(item)
  return ret
},[])

//遍歷並過濾輸入的模塊
moduleName.split(',').filter(item=>{ 
 let ret= modules.indexOf(item)>-1
  if(!ret){ //未配置端口號
    console.log(chalk.yellow(`${item} module not found port config;please check in /config/local.js \n`))
  }
  return ret
}).map(item=>{  // 啓動子進程
  exec(`${line} ${item}`,(error,stdout,stdErr)=>{
    console.log(stdErr)
    error && console.log(error)
  })
  console.log(chalk.green(`${item} running  at  http://localhost:${port[item]}/${item}.html`))
})複製代碼

在package.json scripts加入

"server": "set NODE_ENV='development' && node build/server.js --module",
"server:mock": "set NODE_ENV='development' && node build/server.js --mock --module",複製代碼

在命令行 輸入 "npm run server home,car"  便可啓動 home,car兩個模塊

本地模塊間的頁面跳轉

獲取本地服務端口配置,

const { router } = require(IS_DEV ? "../../../config/local.js" : "../../../config/prod.js")複製代碼

頁面跳轉方法

go (param) {
    let { url, data } = param
    let r = ""
    if (data) {
      Object.keys(data).map(item => { //處理頁面傳參
        r += `&${item}=${data[item]}`
      })
      r = r.slice(1)
    }
    url = url.replace(/(\.html)/, `$1?${r}`) //注入頁面傳參
    const m = url.split(".")[0] 
    location.href = window.encodeURI(`http://localhost:${router[m]}/${url}`)  },複製代碼

如跳轉到 home.html/#/index 頁

App.go(
  {
    url:'home.html/#/index',
    data:{
        id:1           
    }
  }
)複製代碼

所有打包

安裝 glob 用於掃描目錄文件

npm i glob -D複製代碼

在build文件夾下新建一個buildAll.js文件

const glob=require('glob')
const {exec}=require('child_process')
const path=require('path')
const chalk=require('chalk')

glob(path.join(process.cwd(),'/src/modules/*/main.js'),(err,files)=>{ //掃描指定目錄下的入口文件  
console.log(files)  
files.map(item=>{
    let name=item.replace(/(\/main.js)$/,'')
    let n=name.slice(name.lastIndexOf('/')+1)  //解析出模塊名
    console.log(n)
    exec(`set NODE_ENV=production && node build/build.js --module ${n}`,(error,stdout,stdErr)=>{ //調起子進程
      console.log(stdout,stdErr) 
     if(error){
        console.log(error)
        process.exit()
      }
    })
  })
})複製代碼

在package.json scripts中加入

"buildAll": "node build/buildAll.js"複製代碼

命令行輸入 npm run buildAll 就可所有打包了

github地址


github.com/nicole11223…

相關文章
相關標籤/搜索