您好,登錄后才能下訂單哦!
這篇文章主要介紹“javascript怎么實現頁面生成器”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“javascript怎么實現頁面生成器”文章能幫助大家解決問題。
我們的目標是實現一個頁面制作后臺,在后臺中我們可以對頁面進行 組件選擇 --> 布局樣式調整 --> 發布上線 --> 編輯修改
這樣的流程操作。
首先是要能提供組件給用戶進行選擇,那么我們需要一個組件庫
,然后需要對選擇的組件進行布局樣式調整,所以我們需要一個頁面編輯后臺
接著我們需要將編輯產出的數據渲染成真實的頁面,所以我們需要一個node服務
和用于填充的template 模板
。發布上線,這個直接對接各個公司內部的發布系統就好了,這里我們不做過多闡述。最后的編輯修改功能也就是針對配置的修改,所以我們需要一個數據庫,這里我選擇的是用了mysql 。當然你也可以順便做做權限管理,頁面管理....等等之類的活。
啰嗦了這么長,我們來畫個圖,了解下大概的流程:
首先我們來實現組件這一部分,因為組件關聯著后臺編輯的預覽和最后發布的使用。組件設計我們應該盡量保持組件的對外一致性,這樣在進行渲染的時候,我們可以提供一個統一的對外數據接口。這里我們的技術選型是基于 Vue 的,所以下面的代碼部分也主要是基于 Vue 的,但是萬變不離其宗,其他語言也類似。
根據上圖,我們的組件是會被一個個拆分單獨發布到 npm
倉庫的,為什么這么設計呢?其實之前也考慮過設計成一個組件庫,所有組件都包含在一個組件庫內,這樣只需要發布一個組件庫包,用的時候按需加載就好了。后來在實踐的過程中發現這樣并不合適協同開發,其他前端如果想貢獻組件,接入的改造成本也很大。舉個例子:小明在業務中寫了個Button
組件,這個組件經常會被其他項目復用,他想把這個組件貢獻到我們的系統中,被模板使用,如果是一個組件庫的話,他首先得拉取我們組件庫的代碼,然后按照組件庫的規范格式進行提交。這樣一來,偷懶的小明可能就不太愿意這么干,最爽的方法當然是在本地構建一個npm庫,開發選用的是用TypeScript
還是其他的我們不關心,選用的 Css 預處理器我們也不關心,甚至編碼規范的ESLint
我們也不關心。最后只需通過編譯后的文件即可。這樣就避免了一個組件庫的約束。依托于NPM完善的發布/拉取,以及版本控制機制,可以讓我們少做一些額外的工作,也可以快速的把平臺搭建起來。
說了這么多,代碼呢?,我們以一個Button
為例,我們對外提供這樣的形式組件:
<template> <div :style="data.style.container" class="w_button_container"> <button :style="data.style.btn"> {{data.context}}</button> </div> </template> <script> export default { name: 'WButton', props: { data: { type: Object, default: () => {} } } } </script>
可以看到我們只對外暴露了一個props
,這樣做法的好處是可以統一組件對外暴露的數據,組件內部愛怎么玩怎么玩。注意,這里我們也可以引入一些第三方組件庫,比如mint-ui
之類的。
在寫代碼前,我們先考慮一下需要實現哪些功能:
一個屬性編輯區,提供給使用者編輯組件內部props
的功能
一個組件選擇區,提供使用者選擇需要的組件
一個組件預覽區,提供使用者拖拽排序頁面預覽的功能
按照順序,我們先來實現組件的屬性編輯功能。我們要考慮,一個組件暴露出哪些可配置的信息。這些可配置的信息如何同步到后臺編輯區,讓使用者進行編輯,一個按鈕的可配置信息可能是這樣:
如果把這些配置全部寫在后臺庫里面,根據當前選擇的組件加載不同的配置,維護起來會相當麻煩,而且隨著組件數量的增加,也會變得臃腫,所以我們可以將這些配置存儲在服務端,后臺只需要根據存儲的規則進行解析便可,舉個例子,我們其實可以存儲這樣的編輯配置:
[ { "blockName": "按鈕布局設置", "settings": { "src": { "type": "input", "require": true, "label": "按鈕文案" } } } ]
我們在編輯后臺,通過接口請求到這些配置,便可以進行規則渲染:
/** * 根據類型,選擇創建對應的組件 * @param {VNode} vm * @returns {any} */ createEditorElement (vm: VNode) { let dom = null switch (vm.config.type) { case 'align': dom = this.createAlignElement(vm) break; case 'select': dom = this.createSelectElement(vm) break; case 'actions': dom = this.createActionElement(vm) break; case 'vue-editor': dom = this.createVueEditor(vm) break; default: dom = this.createBasicElement(vm) } return dom }
首先我們需要考慮的是,組件怎么進行注冊?因為組件被用戶選用的時候,我們是需要渲染該組件的,所以我們可以提供一段 node 腳本來遍歷所需組件,進行組件的安裝注冊:
// 定義渲染模板和路徑 var OUTPUT_PATH = path.join(__dirname, '../packages/index.js'); console.log(chalk.yellow('正在生成包引用文件...')) var INSTALL_COMPONENT_TEMPLATE = ' {{name}}'; var IMPORT_TEMPLATE = 'import {{componentName}} from \\\\'{{name}}\\\\''; var MAIN_TEMPLATE = `/* Automatic generated by './compiler/build-entry.js' */ {{include}} const components = [ {{install}} ] const install = function(Vue) { components.map((component) => { Vue.component(component.name, component) }) } /* istanbul ignore if */ if (typeof window !== 'undefined' && window.Vue) { install(window.Vue) } export { install, {{list}} } `; // 渲染引用文件 var template = render(MAIN_TEMPLATE, { include: includeComponentTemplate.join(endOfLine), install: installTemplate.join(`,${endOfLine}`), version: process.env.VERSION || require('../package.json').version, list: listTemplate.join(`,${endOfLine}`) }); // 寫入引用 fs.writeFileSync(OUTPUT_PATH, template);
最后渲染出來的文件大概是這樣:
import WButton from 'w-button' const components = [ WButton ] const install = function(Vue) { components.map((component) => { Vue.component(component.name, component) }) } /* istanbul ignore if */ if (typeof window !== 'undefined' && window.Vue) { install(window.Vue) } export { install, WButton }
這個也是組件庫的通用寫法,所以這里的思想就是把發布到npm
上的組件,進行聚合,聚合成一個組件包引用,我們在后臺編輯的時候,是需要全量引入的:
import * as W_UI from '../../packages' Vue.use(W_UI)
這樣,我們組件便注冊完了,組件選擇區,主要是提供組件的可選項,我們可以遍歷組件,提供一個個 List 讓用戶選擇,當然如果我們每個組件如果只提供一個組件名,用戶可能并不知道組件長什么樣,所以我們最好可以提供一下組件長什么樣的縮略圖。這里我們可以在組件發布的時候,也通過 node 腳本進行。這里要實現的代碼比較多,我就大致說一下過程,因為也不是核心邏輯,可有可無,只能說有了體驗上會好一點:
用戶啟用 dev-server 進行代碼編寫測試
server 腳本使用 Chrome 工具 puppeteer
,調整頁面到手機端模式, 進行當前 dev-server 截圖。
生成截圖文件,上傳到node服務,關聯組件
這樣,就可以在加載組件選擇區的時候,為組件附上縮略圖。
當用戶在選擇區選擇了組件,我們需要展示在預覽區域,那么我們怎么知道用戶選擇了哪些組件呢?總不能提前全部把組件寫入渲染區域,通過 v-if
來判斷選擇吧?當然沒有這么蠢,Vue 已經提供了動態組件的功能了:
<div :class="[index===currentEditor ? 'active' : '']" :is="select.name" :data="select.data"> </div>
為什么我們不用縮略圖代替真實組件?一方面生成的縮略圖尺寸存在問題,另一方面,我們需要編輯的聯動性,就是編輯區的編輯需要及時的反饋給用戶。
說了這么多,貌似一切都很順利,但是這樣在實踐的時候,發現了存在一個明顯的問題就是:我們中間的預覽區域其實就是為了盡可能模擬移動端頁面效果。但是如果我們加入了一些包含類似 position: fixed
樣式的組件,會發現樣式上就出現了明顯的問題。典型的比如Dialog Loading
等。
所以我們參考了 m-ui
組件庫的設計,將中間預覽操作容器展示為一個iframe
。將iframe
大小調整為375 * 667
,模擬 iPhone 6 的手機端。這樣就不會存在樣式問題了。可是這樣又出現了另一個難點,那就是左側的編輯數據如何及時的反應到iframe
中?沒錯,就是postMessgae
,大致思路如下:
利用 vuex
做數據存儲池,所有的變化,通過 postMessgae
進行同步,這樣我們只用確保數據池中的數據變化,便可以映射到渲染層的變化。比如,我們在預覽區進行了組件選擇和拖拽排序,那么我們只需通過vuex
出發同步信息便可:
// action.ts const action = { setCurrentPage ({commit, state}, page: number) { // 更新當前store commit('setCurrentPage',page) // 對應postMessage helper.postMsgToChild({type: 'syncState', value: state}) }, // ... }
模板的設計實現,我參考了 Vue-cli 2.x
版本的思想,把這里的模板,存在了對應的 git
倉庫中。當用戶需要進行頁面構建的時候,直接從 git 倉庫中拉取對應的模板即可。當然拉取完,也會緩存一份在本地,以后渲染,直接從本地緩存中讀取即可。我們現在把中心放在模板的格式和規范上。模板我們采用什么樣的語法無所謂,這里我才用了和 Vue-cli
一樣的Handlerbars
引擎。這里直接上我們模板的設計:
<template> <div class="pg-index" :style="{backgroundColor: '{{bgColor}}'}"> <div class="main-container" :style="{ backgroundColor: '{{bgColor}}', backgroundImage: '{{bgImage}}' ? 'url({{bgImage}})' : null, backgroundSize: '{{bgSize}}', backgroundRepeat: 'no-repeat' }"> {{#components}} <div class="cp-module-editor {{className}} {{data.className}}"> <{{name}} class="temp-component" :data="{{tostring data}}" data-type="{{upcasefirst name}}"></{{name}}> </div> {{/components}} </div> </div> </template> <script> {{#noRepeatCpsName}} import {{upcasefirst this}} from '{{this}}' {{/noRepeatCpsName}} export default { name: '{{upcasefirst repoName}}', components: { {{#noRepeatCpsName}} {{upcasefirst this}}, {{/noRepeatCpsName}} } } </script>
為了簡化邏輯,我們把模板都設計成流式布局,所有組件一個個堆疊往下順序排列。這個文件便是我們vue-webpack-simple
的模板中的App.vue
。我們對其進行了改寫。這樣在數據填充萬,便可以渲染出一個 Vue 單文件。這里我只舉著一個例子,我們還可以實現多頁模板等等復雜的模板,根據需求拉取不同的模板即可。
當后臺提交渲染請求的時候,我們的 node 服務所做的工作主要是:
拉取對應模板
渲染數據
編譯
拉取也就是去指定倉庫中通過download-git-repo
插件進行拉取模板。編譯其實也就是通過metalsmith
靜態模板生成器把模板作為輸入,數據作為填充,按照handlebars
的語法進行規則渲染。最后產出build
構建好的目錄。在這一步,我們之前所需的組件,會被渲染進package.json
文件。我們來看一下核心代碼:
// 這里就像一個管道,以數據入口為生成源,通過renderTemplateFiles編譯產出到目標目錄 function build(data, temp_dest, source, dest, cb) { let metalsmith = Metalsmith(temp_dest) .use(renderTemplateFiles(data)) .source(source) .destination(dest) .clean(false) return metalsmith.build((error, files) => { if (error) console.log(error); let f = Object.keys(files) .filter(o => fs.existsSync(path.join(dest, o))) .map(o => path.join(dest, o)) cb(error, f) }) } function renderTemplateFiles(data) { return function (files) { Object.keys(files).forEach((fileName) => { let file = files[fileName] // 渲染方法 file.contents = Handlebars.compile(file.contents.toString())(data) }) } }
最后我們得到的是一個 Vue 項目,此時還不能直接跑在瀏覽器端,這里就涉及到當前發布系統所支持的形式了。怎么說?如果你的公司發布系統需要在線編譯,那么你可以把源文件直接上傳到 git 倉庫,觸發倉庫的 WebHook 讓發布系統替你發掉這個項目即可。如果你們的發布系統是需要你編譯后提交編譯文件進行發布的,那么你可以通過 node 命令,進行本地構建,產出 HTML ,CSS ,JS 。直接提交給發布系統即可。
關于“javascript怎么實現頁面生成器”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。