这是一篇带有整活性质的教程,但是——它是一篇真实的、具有可操作性的教程。
我真的在试图教会你从零搭建一个单页面应用程序 (Single Page Application)!
你也许已经知道了,MediaWiki 应用程序会在全局对象上绑定一个 mw.config 对象,该对象上保存了一些 wiki 的基本信息。
如果你还不知道这一点,你可以按 F12 打开你的浏览器控制台,打印一下这个对象。
const mwWikiData = mw.config.values
你之后会用到它。
我们可以简单直接地克隆一份文章区域的 DOM 节点:
const mwContentText = document
.querySelector('#mw-content-text')
?.cloneNode(true)
/** ↑ 注意这里的 true
* 我们顺便克隆了文章区域的DOM上绑定的事件
* 这样做可以保留住[[T:Hide]]等模板的交互性
*/
现在你得到的数据已经足以支撑构建一个新皮肤了。
首先,让我们找到一个根节点,我们将会用我们构建的皮肤替换其中的内容。
这个节点的最佳人选——额,我不知道该如何措辞,不过我猜你知道我的意思——就是当前皮肤所有内容除 body 元素以外最顶层的父级元素,例如对于 MoeSkin 来说,div#app 就是最好的选择。
现在你有一个根节点了,是时候开始创作了。选择一个你最熟悉的实现方案,或者把每一种方案都尝试一遍,我会确保教程中的每一种实现方案都是切实可行的。
大家都喜欢 Vanilla JS。Vanilla JS 是一个快速、轻量级、跨平台的 JavaScript 框架。我们可以用它构建强大的 JavaScript 应用程序。
全世界的互联网企业都在使用它——没错,所有!因此,自信点,想想看,现在你正在使用与 Google 和 Facebook 等国际企业相同的技术去编写专属于你自己的萌娘百科皮肤!
首先我们需要一个页顶,里面是网站的名字:
const siteName = document.createElement('h1')
siteName.innerText = mw.config.get('wgSiteName')
const header = document.createElement('header')
header.appendChild(siteName)
接下来,安排条目的名字和条目的内容:
const article = document.createElement('article')
const firstHeading = document.createElement('h1')
firstHeading.classList.add('firstHeading')
firstHeading.id = 'firstHeading'
firstHeading.innerText = mw.config.get('wgPageName')
article.append(
firstHeading,
mwContentText // 注意:这个变量是我们在准备工作时取得的
)
要不我们再往页脚里添加一些东西?比如欢迎当前的用户访问:
const footer = document.createElement('footer')
footer.innerText = `欢迎光临,${mw.config.get('wgUserName')}`
好了,齐活了,我们用它替换根元素里的内容吧:
const root = document.querySelector('body > div:first-of-type')
root.innerHTML = ''
root.append(header, article, footer)
最后,我们将代码封入一个立即调用函数表达式,然后来完整看一遍:
/**
* My first MGP skin - Vanilla JS ver.
* @author <你的名字>
*/
;(() => {
// Header
const siteName = document.createElement('h1')
siteName.innerText = mw.config.get('wgSiteName')
const header = document.createElement('header')
header.append(siteName)
// Article
const mwContentText = document
.querySelector('#mw-content-text')
?.cloneNode(true)
const article = document.createElement('article')
const firstHeading = document.createElement('h1')
firstHeading.classList.add('firstHeading')
firstHeading.id = 'firstHeading'
firstHeading.innerText = mw.config.get('wgPageName')
article.append(firstHeading, mwContentText)
// Footer
const footer = document.createElement('footer')
footer.innerText = `欢迎光临,${mw.config.get('wgUserName')}`
// Mount
const root = document.querySelector('body > div:first-of-type')
root.innerHTML = ''
root.append(header, article, footer)
})()
把这段代码复制粘贴到浏览器控制台,执行它。没错,你已经成功地打造了一个简单的皮肤。
很多人的 JavaScript 编程之路都是从 jQuery 开始的。
虽然它是一个比较古早的工具库,不过它真的很好上手!
鉴于实现思路与 Vanilla JS 没有太大的差异,直接贴出最终的源码好了:
/**
* My first MGP skin - jQuery ver.
* @author <你的名字>
*/
$(function () {
// Header
const siteName = $('<h1>').text(mw.config.get('wgSiteName'))
const header = $('<header>')
header.append(siteName)
// Article
const mwContentText = $('#mw-content-text').clone(true)
const article = $('<article>')
const firstHeading = $('<h1>', {
class: 'firstHeading',
id: 'firstHeading',
}).text(mw.config.get('wgPageName'))
article.append(firstHeading, mwContentText)
// Footer
const footer = $('<footer>').text(`欢迎光临,${mw.config.get('wgUserName')}`)
// Mount
const root = $('body > div:first-of-type')
root.empty()
root.append(header, article, footer)
})
实现看上去简洁了不少,不得不感慨一句,不愧是 jQuery 的顶级封装。
其实你会发现,如果没有任何交互,使用 Vue 构建网页和 jQuery 看上去竟然没有多大区别!
import(
'https://cdn.bootcdn.net/ajax/libs/vue/3.2.45/vue.esm-browser.prod.min.js'
).then((Vue) => {
const { createApp, h, defineComponent, ref, onMounted, nextTick } = Vue
// Header
const siteName = h('h1', {}, mw.config.get('wgSiteName'))
const header = h('header', {}, [siteName])
// Article
const mwContentText = document
.querySelector('#mw-content-text')
?.cloneNode(true)
const firstHeading = h('h1', {
class: 'firstHeading',
id: 'firstHeading',
text: mw.config.get('wgPageName'),
})
const article = h('article', {}, [
firstHeading,
h('div', { id: 'mw-content-container' }),
])
// Footer
const footer = h('footer', {}, [`欢迎光临,${mw.config.get('wgUserName')}`])
// Mount
const App = defineComponent({
render() {
return h('div', { ref: 'appRef' }, [header, article, footer])
},
setup() {
const appRef = ref()
onMounted(async () => {
await nextTick()
appRef.value
.querySelector('#mw-content-container')
.append(mwContentText)
})
return { appRef }
},
})
createApp(App).mount('body > div:first-of-type')
})
没错,我正在在安利你来用 Vue
施工中(不过在使用纯渲染函数的情况下,代码看上去会和 vue 基本上差不多)