最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

webpack-mvc 傳統(tǒng)多頁(yè)面組件化開(kāi)發(fā)詳解

 更新時(shí)間:2019年05月07日 09:44:20   作者:shortlife  
這篇文章主要介紹了webpack-mvc 傳統(tǒng)多頁(yè)面組件化開(kāi)發(fā)詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

最近有一個(gè)項(xiàng)目,還是使用的傳統(tǒng) MVC 模式開(kāi)發(fā),完全基于jQuery,使用了基于java模板引擎velocity,頁(yè)面中嵌入了大量java語(yǔ)法,使得前后端分離不徹底,工程打包上線苦不堪言,為實(shí)現(xiàn)后端為服務(wù)化,前端也得徹底從后端中分離出來(lái)。

方案: webpack4 + ejs

webpack

  • 打包所有的 資源
  • 打包所以的 腳本
  • 打包所以的 圖片
  • 打包所以的 樣式
  • 打包所以的 表

ejs

高效的 JavaScript 模板引擎,代替 velocity

webpack 配置

基本插件

  • @babel/core,@babel/preset-env,babel-loader

es6 語(yǔ)法轉(zhuǎn)譯

  • css-loader,style-loader

編譯打包c(diǎn)ss

  • node-sass,sass-loader

解析sass

  • postcss-loader,autoprefixer

自動(dòng)給樣式增加瀏覽器前綴

  • mini-css-extract-plugin

將css從js中抽離出來(lái)為單獨(dú)文件

  • optimize-css-assets-webpack-plugin

壓縮css

  • uglifyjs-webpack-plugin

壓縮js

  • ejs-loader

解析ejs模板文件

  • html-webpack-plugin

生成html文件

  • rimraf

刪除文件、文件夾

  • watch

監(jiān)聽(tīng)文件變化

上面是一些要用的插件,具體用法不累述。

入口文件

入口文件長(zhǎng)這樣(可單一入口,也可多入口):

// 多入口
entry: {
 pageA: './src/pageA/index.js',
 pageB: './src/pageB/index.js',
 'pageC/login': './src/pageC/login/login.js'
}

出口文件:

output: {
 filename: '[name].js',
 path: path.resolve(__dirname, '../dist'),
}

filename 值中的 [name] 對(duì)應(yīng)入文件的 key 值,/ 分割文件夾。

最后就會(huì)在dist文件夾下生產(chǎn)文件:

  • dist/pageA/index.js
  • dist/pageB/index.js
  • dist/pageC/login/login.js

既然是多頁(yè)面開(kāi)發(fā),就要有多個(gè)入口,每個(gè)頁(yè)面都要有自己對(duì)應(yīng)的js入口,這樣我們只需要遍歷html文件,然后找到對(duì)應(yīng)的js,處理成 entry 對(duì)象即可

const path = require('path')
const glob = require('glob')

const pages = (entries => {
 let entry = {}, htmlArr = []
 // 格式化生成入口
 entries.forEach((file) => {
  // ...../webpack-mvc/src/page/pageA/index.html
  const fileSplit = file.split('/')
  const length = fileSplit.length

  // 頁(yè)面入口 pageA/index.html
  const filePath = fileSplit.slice(length - 2, length).join('/') 

  // 根據(jù)html路徑找到對(duì)應(yīng)的js路徑,js可以和html放在同一文件夾,也可單獨(dú)放在一個(gè)文件夾內(nèi),只要能找到 
  const jsPath = path.resolve(__dirname, `../src/page/${filePath.split('.')[0]}.js`) 

  // _main.ejs 頁(yè)面主題框架,html組件化
  pageHtml = path.resolve(__dirname, '../src/_main.ejs') 

  if (!fs.existsSync(jsPath)) {
   return;
  }
  entry['js/' + filePath.split('.')[0]] = jsPath // 加 js/ 即表示將打包后的js單獨(dú)放在一個(gè)文件夾內(nèi)
 })
 return entry
})(glob(path.resolve(__dirname, '../src/page/*/*.html'), {sync: true}))

上面只是本例的目錄結(jié)構(gòu),根據(jù)不同的目錄結(jié)構(gòu),更改路徑即可,目的就是得到 ‘js打包生成路徑': ‘入口js' 映射關(guān)系。

html(ejs) 組件化

頁(yè)面框架

1、主體框架 src/_main.ejs

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width,initial-scale=1.0">
 <title><%= htmlWebpackPlugin.options.title %></title>
</head>

<body>
 <div class="main-head">
  <%= require('@/common/components/header/header.ejs')() %>
 </div>

 <div class="main-content">
  <%= htmlWebpackPlugin.options.content %>
 </div>

 <div class="main-foot">
  <%= require('@/common/components/footer/footer.ejs')() %>
 </div>
</body>

</html>

2、公共頁(yè)面

header、footer每個(gè)頁(yè)面都包含,所以放入主體框架頁(yè)面內(nèi)

3、頁(yè)面各自部分

各個(gè)頁(yè)面只需要寫(xiě)自己頁(yè)面的html內(nèi)容即可,并且還可以引入公共組件ejs

// pageA/index.html
<div>
 <h1>pageA index</h1>
</div>

// pageA/login.html
<div>
 <%= require('@/common/components/form.ejs')() %>
 <h1>pageA login</h1>
</div>

網(wǎng)上查了很多資料,沒(méi)找到可以實(shí)現(xiàn)上面步驟的方法,基本都是要在每個(gè)頁(yè)面的js里去寫(xiě)一些ejs語(yǔ)法,做不到我想要的只關(guān)注此頁(yè)面本身的內(nèi)容。

替換 _main.ejs,生成臨時(shí)模板

我的解決方法是 通過(guò) node 讀取頁(yè)面 html 文件,然后替換 _main.ejs 中的 content 部分,生成一個(gè)臨時(shí) ejs 模板文件,然后通過(guò)插件 html-webpack-plugin 生成最終頁(yè)面 html 文件

function createTemplate(file, jsPath, entry) {
 let obj = {
  title: '',
  template: '',
  filename: '',
  chunks: [jsPath]
 }
 // _main.ejs 頁(yè)面主題框架,html組件化
 let mainHtml = path.resolve(__dirname, '../src/_main.ejs')
 let fileSplit = file.split('/')
 // html 生成路徑
 let filename = fileSplit.slice(fileSplit.length - 2).join('/').split('.')[0];

 let strContent = fs.readFileSync(file, 'utf-8')
 let strMain = fs.readFileSync(mainHtml, 'utf-8')
 let template = fileSplit.slice(fileSplit.length - 2).join('_').split('.')[0];
 strMain = strMain.replace(/<%= htmlWebpackPlugin.options.content %>/, strContent)
 fs.writeFileSync(path.resolve(__dirname, `../src/template/template_${template}.ejs`), strMain)

 obj.template = path.resolve(__dirname, `../src/template/template_${template}.ejs`)
 obj.filename = filename
 return obj
}

有了上面方法的思路,我們可以在各自頁(yè)面中做更多的操作

頁(yè)面 title

// pageA/index.html

<%=title 頁(yè)面A %>
<div>
 <h1>pageA index</h1>
</div>

頁(yè)面直接引入js,只壓縮不打包

// pageA/index.html

<%=title 頁(yè)面A %>

<div>
 <h1>pageA index</h1>
</div>

<script src="js/common/util.js"></script>
<script src="js/common/server.api.js"></script>

這里引入js的路徑是最終文件壓縮生成的位置(dist目錄下),因?yàn)殚_(kāi)發(fā)模式和生產(chǎn)環(huán)境路徑有所不同,所以等下在代碼中要區(qū)別不同環(huán)境去替換不同的路徑。

頁(yè)面引入ejs組件

// pageA/index.html

<%=title 頁(yè)面A %>

<div>
 <%= require('@/common/components/form.ejs')() %>
 <h1>pageA index</h1>
</div>

<script src="js/common/util.js"></script>
<script src="js/common/server.api.js"></script>

page.config.js

const fs = require('fs')
const path = require('path')
const glob = require('glob')

if (process.env.NODE_ENV === 'development') {
 const rimraf = require('rimraf')
 rimraf.sync(path.resolve(__dirname, '../src/template/*'), fs, function cb() {
  console.log('template目錄已清空')
 })
}

const pages = (entries => {
 let entry = {}, htmlArr = []
 // 格式化生成入口
 entries.forEach((file) => {
  // ...../webpack-mvc/src/page/pageA/index.html
  let fileSplit = file.split('/')
  let length = fileSplit.length

  // 頁(yè)面入口 page/pageA/index.html
  let filePath = fileSplit.slice(length - 3, length).join('/')

  // 根據(jù)html路徑找到對(duì)應(yīng)的js路徑,js可以和html放在同一文件夾,也可單獨(dú)放在一個(gè)文件夾內(nèi),只要能找到
  let jsFile = path.resolve(__dirname, `../src/${filePath.split('.')[0]}.js`)
  if (!fs.existsSync(jsFile)) {
   return;
  }
  let jsPath = 'js/' + filePath.split('.')[0]
  entry['js/' + filePath.split('.')[0]] = jsFile
  htmlArr.push(createTemplate(file, jsPath, entry))
 })
 return {entry, htmlArr}
})(glob(path.resolve(__dirname, '../src/page/*/*.html'), {sync: true}))

function scriptLinkEntry(entry, file) {
 // file: /js/common/js/util.js
 let fileNew = './src/' + file.split('/').slice(2).join('/')
 let fileSplit = fileNew.split('/')
 entry['js/common/' + fileSplit.slice(fileSplit.length - 1).join('/').replace('.js', '')] = fileNew
}

function replaceScript(content, entry) {
 let scriptLink = content.match(/<script.*src=["|'](.*)["|']><\/script>/g)
 if (scriptLink) {
  scriptLink.forEach(item => {
   // src: /js/common/js/util.js
   let src = item.match(/src=["|'](.*)["|']/)[1];
   scriptLinkEntry(entry, src)
   let scriptlinNew = src
   // 生產(chǎn)環(huán)境根據(jù)頁(yè)面路徑找到j(luò)s的相對(duì)路徑,開(kāi)發(fā)環(huán)境 /js/ 指向 dist 目錄下 js 文件夾
   if (process.env.NODE_ENV === 'production') {
    let srcSplit = src.split('/')
    srcSplit.splice(3, 1) // ['', 'js', 'common', 'util.js']
    scriptLinkNew = `..${srcSplit.join('/')}` // ../js/common/util.js
   }
   content = content.replace(src, scriptLinkNew) 
  })
 }
 return content;
}

function createTemplate(file, jsPath, entry) {
 let obj = {
  title: '',
  template: '',
  filename: '',
  chunks: [jsPath]
 }
 // _main.ejs 頁(yè)面主題框架,html組件化
 let mainHtml = path.resolve(__dirname, '../src/_main.ejs')
 let fileSplit = file.split('/')
 // html 生成路徑
 let filename = fileSplit.slice(fileSplit.length - 2).join('/').split('.')[0];

 let strContent = fs.readFileSync(file, 'utf-8')
 let strMain = fs.readFileSync(mainHtml, 'utf-8')
 let template = fileSplit.slice(fileSplit.length - 2).join('_').split('.')[0]

 // 提取頁(yè)面title
 let titleMatch = strContent.match(/<%=title(.*)%>/)
 let title = ''
 if (titleMatch) {
  title = titleMatch[1]
  strContent = strContent.replace(/<%=title(.*)%>/, '')
 }

 // 提取頁(yè)面與主體框架中引入的靜態(tài)js文件,將其放入入口文件中經(jīng)行壓縮,并適應(yīng)開(kāi)發(fā)與生產(chǎn)路徑
 strMain = replaceScript(strMain, entry)
 strContent = replaceScript(strContent, entry)

 strMain = strMain.replace(/<%= htmlWebpackPlugin.options.content %>/, strContent)
 fs.writeFileSync(path.resolve(__dirname, `../src/template/template_${template}.ejs`), strMain)

 obj.title = title
 obj.template = path.resolve(__dirname, `../src/template/template_${template}.ejs`)
 obj.filename = filename
 return obj
}

module.exports = pages;

熱刷新

此時(shí)熱刷新只能監(jiān)聽(tīng)到j(luò)s和css的改變,因?yàn)槟0迨莿?dòng)態(tài)生成的,更改頁(yè)面內(nèi)容時(shí)模板并沒(méi)有改變,所以無(wú)法觸發(fā)devServer的熱刷新,手動(dòng)刷新也不會(huì)有變化,因?yàn)榕R時(shí)模板文件沒(méi)有改變,借用插件 watch 來(lái)監(jiān)聽(tīng)html文件變化,然后重寫(xiě)模板文件可解決問(wèn)題。

const fs = require('fs')
const path = require('path')
const watch = require('watch')
const { replaceScript } = require('./page.config.js')

watch.watchTree(path.resolve(__dirname, '../src/page'), (f, curr, prev) => {
 if (typeof f == 'object' && prev === null && curr === null) {
  // Finished walking the tree
 } else if (prev === null) {
  // f is a new file
  createTemplate(f)
 } else if (curr.link === 0) {
  // f was removed
 } else {
  createTemplate(f)
 }
})

function createTemplate(file) {
 if (file.indexOf('.html') === -1) {
  return
 }
 console.log('file', file)
 let mainHtml = path.resolve(__dirname, '../src/_main.ejs')
 let strContent = fs.readFileSync(file, 'utf-8')
 let strMain = fs.readFileSync(mainHtml, 'utf-8')
 let template = file.split('\\').slice(file.split('\\').length - 2).join('_').split('.')[0]
 // 提取頁(yè)面與主體框架中引入的靜態(tài)js文件,將其放入入口文件中經(jīng)行壓縮,并適應(yīng)開(kāi)發(fā)與生產(chǎn)路徑
 // 這里不再處理 title 和 靜態(tài)js 入口壓縮
 strMain = replaceScript(strMain, {}, true)
 strContent = replaceScript(strContent, {}, true)
 strContent = strContent.replace(/<%=(.*)%>/, '')
 strMain = strMain.replace(/<%= htmlWebpackPlugin.options.content %>/, strContent)
 fs.writeFileSync(path.resolve(__dirname, `../src/template/template_${template}.ejs`), strMain)
}

這里不再處理title和靜態(tài)js入口壓縮,更改了這些只能再重新 npm run dev

國(guó)際化

const languageProperty = require('../properties/language.properties.js')

function getLanText(val) {
 let lan = 'zh' // $.cookie('lan')
 let str = languageProperty[val] && languageProperty[val][lan] || val
 let defaultOpt = languageProperty[val] && languageProperty[val]['default']
 let opts = defaultOpt && $.extend(true, [], defaultOpt)
 opts ? opts.unshift('') : false
 let args = opts && arguments.length === 1 ? opts : arguments
 if (args.length > 1) {
  let params = Array.property.slice.call(args, 1)
  return str.replace(/{(\d+)}/g, function(curr, index) {
   return params[index]
  })
 } else {
  return str
 }
}

function translateAll() {
 let num = $('html').find('[lang]').length
 let count = 0
 if (num === 0) {
  $('body').show()
 }
 $('html').find('[lang]').each(function() {
  count += 1;
  let lang = $(this).attr('lang')
  if (lang === '') {
   return;
  }
  let nodeName = $(this)[0].nodeName
  let text = getLanText(lang)
  // 簡(jiǎn)單處理,復(fù)雜的可再這里更改
  if (nodeName === 'INPUT') {
   $(this).attr('placeholder', text)
  } else {
   $(this).html(text)
  }
  if (count === num) {
   $('body').show()
  }
 })
}

module.exports = { getLanText, translateAll }

在header.js里調(diào)用一次就可以了。

結(jié)語(yǔ)

至此,傳統(tǒng)多頁(yè)面組件化開(kāi)發(fā)流程基本完成,可以完全脫離后臺(tái)愉快的開(kāi)發(fā)前端了,拋棄eclipse,擁抱vsCode。

此文只構(gòu)建了基本的框架,中間還有很多優(yōu)化點(diǎn),打包速度,公共代碼等等都沒(méi)有去細(xì)究,等頁(yè)面、代碼量增加,這也是必須去研究的,路漫漫其修遠(yuǎn)兮。

Guthub 可直接 npm run dev, npm run build 運(yùn)行, 順便求個(gè)Star 😄

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • JS 組件系列之Bootstrap Table的凍結(jié)列功能徹底解決高度問(wèn)題

    JS 組件系列之Bootstrap Table的凍結(jié)列功能徹底解決高度問(wèn)題

    這篇文章主要介紹了JS 組件系列之Bootstrap Table的凍結(jié)列功能徹底解決高度問(wèn)題,需要的朋友可以參考下
    2017-06-06
  • 小程序開(kāi)發(fā)實(shí)戰(zhàn)指南之封裝自定義彈窗組件

    小程序開(kāi)發(fā)實(shí)戰(zhàn)指南之封裝自定義彈窗組件

    最近在做公司的小程序項(xiàng)目,發(fā)現(xiàn)設(shè)計(jì)上有很多不統(tǒng)一,代碼上有很多冗余,下面這篇文章主要給大家介紹了關(guān)于小程序開(kāi)發(fā)實(shí)戰(zhàn)指南之封裝自定義彈窗組件的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • 一文搞懂?parseInt()函數(shù)異常行為

    一文搞懂?parseInt()函數(shù)異常行為

    這篇文章主要為大家介紹了一文搞懂?parseInt()函數(shù)異常行為示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • JavaScript代碼判斷輸入的字符串是否含有特殊字符和表情代碼實(shí)例

    JavaScript代碼判斷輸入的字符串是否含有特殊字符和表情代碼實(shí)例

    這篇文章主要介紹了JavaScript代碼判斷輸入的字符串是否含有特殊字符和表情,通過(guò)js代碼if語(yǔ)句進(jìn)行判斷,并結(jié)合自己開(kāi)發(fā)的情景,具體操作步驟大家可查看下文的詳細(xì)講解,感興趣的小伙伴們可以參考一下。
    2017-08-08
  • 如何解決hover在ie6中的兼容性問(wèn)題

    如何解決hover在ie6中的兼容性問(wèn)題

    本篇主要分享了如何解決hover在ie6中的兼容性問(wèn)題的方法,希望會(huì)對(duì)大家學(xué)習(xí)javascript有所幫助
    2016-12-12
  • JS解決ajax無(wú)法后退的問(wèn)題記錄

    JS解決ajax無(wú)法后退的問(wèn)題記錄

    Ajax請(qǐng)求通常不支持瀏覽器的后退按鈕,因?yàn)樗鼈兪钱惒降?不會(huì)導(dǎo)致頁(yè)面重新加載(刷新),但如果你想要用戶能夠通過(guò)瀏覽器的后退按鈕回到之前的頁(yè)面狀態(tài),你可以通過(guò)幾種方法來(lái)解決這個(gè)問(wèn)題,感興趣的朋友跟隨小編一起看看吧
    2024-10-10
  • 微信JS SDK接入的幾點(diǎn)注意事項(xiàng)(必看篇)

    微信JS SDK接入的幾點(diǎn)注意事項(xiàng)(必看篇)

    下面小編就為大家?guī)?lái)一篇微信JS SDK接入的幾點(diǎn)注意事項(xiàng)(必看篇)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • 使用JS獲取SessionStorage的值

    使用JS獲取SessionStorage的值

    這篇文章給大家介紹了使用js獲取sessionstorage中的值,首先獲取它是為了將獲得的信息輸出或者alert(),其次,在靜態(tài)頁(yè)面中,如果使用sessionStorage就相當(dāng)于在動(dòng)態(tài)頁(yè)面里連接了數(shù)據(jù)庫(kù)一樣,具體內(nèi)容詳情大家參考下本文
    2018-01-01
  • JS簡(jiǎn)單生成由字母數(shù)字組合隨機(jī)字符串示例

    JS簡(jiǎn)單生成由字母數(shù)字組合隨機(jī)字符串示例

    這篇文章主要介紹了JS簡(jiǎn)單生成由字母數(shù)字組合隨機(jī)字符串,結(jié)合實(shí)例形式分析了javascript使用Math.random()生成隨機(jī)字符串相關(guān)操作技巧,需要的朋友可以參考下
    2018-05-05
  • echarts自定義tooltip中的內(nèi)容代碼示例

    echarts自定義tooltip中的內(nèi)容代碼示例

    在ECharts中,通過(guò)formatter函數(shù)自定義圖例樣式,可以實(shí)現(xiàn)更靈活的圖表展示,滿足特定的視覺(jué)需求,這篇文章主要介紹了echarts自定義tooltip中內(nèi)容的相關(guān)資料,需要的朋友可以參考下
    2024-10-10

最新評(píng)論

崇阳县| 余江县| 兴化市| 临武县| 平利县| 琼结县| 唐海县| 岑巩县| 株洲市| 彩票| 五大连池市| 祁阳县| 广昌县| 南木林县| 肥东县| 永和县| 庆安县| 瑞安市| 扶余县| 周宁县| 罗源县| 佛冈县| 伊川县| 吉水县| 五常市| 琼海市| 临潭县| 周口市| 临安市| 应城市| 九江市| 闽清县| 涿州市| 江西省| 湖州市| 长宁区| 合江县| 德阳市| 板桥市| 青龙| 钦州市|