feat: add mp-webpack-plugin2

This commit is contained in:
dntzhang 2019-08-16 09:38:21 +08:00
parent 41b4d799da
commit 2487db8d79
12 changed files with 1087 additions and 0 deletions

View File

@ -0,0 +1,13 @@
const path = require('path')
module.exports = {
'extends': [
path.resolve(__dirname, '../../.eslintrc.js'),
],
'rules': {
'import/no-unresolved': 'off',
},
'globals': {
'init': true,
},
}

View File

@ -0,0 +1,3 @@
# 更新日志
## next version

View File

@ -0,0 +1,15 @@
# mp-webpack-plugin
## 介绍
一个用于将 vue 组件转化成小程序代码的 webpack 插件。
## 安装
```
npm install --save-dev mp-webpack-plugin
```
## 使用
[查看文档](https://github.com/wechat-miniprogram/kbone/blob/master/docs/quickstart.md)。

View File

@ -0,0 +1,52 @@
{
"_from": "mp-webpack-plugin2",
"_id": "mp-webpack-plugin2@0.0.1",
"_inBundle": false,
"_integrity": "sha512-6hBKApXShnJropp93oV5/z6mnO+1+pG/n7o0nzOY3W80U+8YtcAO19nGXQmVt6yYlcDIR5qTlbfxduv+hGz6Kw==",
"_location": "/mp-webpack-plugin2",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "mp-webpack-plugin2",
"name": "mp-webpack-plugin2",
"escapedName": "mp-webpack-plugin2",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/mp-webpack-plugin2/-/mp-webpack-plugin2-0.0.1.tgz",
"_shasum": "5719cd7fa9fd48af3f48df4503d0aca39e50bfee",
"_spec": "mp-webpack-plugin2",
"_where": "/Users/dntzhang/Documents/GitHub/omi",
"author": {
"name": "wechat-miniprogram"
},
"bugs": {
"url": "https://github.com/wechat-miniprogram/kbone/issues"
},
"bundleDependencies": false,
"dependencies": {
"path-to-regexp": "^3.0.0",
"postcss": "^7.0.17",
"webpack": "^4.35.3",
"webpack-sources": "^1.3.0"
},
"deprecated": false,
"description": "A tool for develop miniprogram.",
"homepage": "https://github.com/wechat-miniprogram/kbone#readme",
"license": "MIT",
"main": "src/index.js",
"miniprogram": "src",
"name": "mp-webpack-plugin2",
"repository": {
"type": "git",
"url": "git+https://github.com/wechat-miniprogram/kbone.git"
},
"scripts": {},
"version": "0.0.1"
}

View File

@ -0,0 +1,248 @@
const path = require('path')
const fs = require('fs')
const ConcatSource = require('webpack-sources').ConcatSource
const ModuleFilenameHelpers = require('webpack/lib/ModuleFilenameHelpers')
const {RawSource} = require('webpack-sources')
const pathToRegexp = require('path-to-regexp')
const adjustCss = require('./tool/adjust-css')
const _ = require('./tool/utils')
const PluginName = 'MpPlugin'
const pageJsTmpl = fs.readFileSync(path.resolve(__dirname, './tmpl/page.tmpl.js'), 'utf8')
const appWxssTmpl = fs.readFileSync(path.resolve(__dirname, './tmpl/app.tmpl.wxss'), 'utf8')
const projectConfigJsonTmpl = require('./tmpl/project.config.tmpl.json')
const packageConfigJsonTmpl = require('./tmpl/package.tmpl.json')
process.env.isMiniprogram = true // 设置环境变量
const globalVars = ['navigator', 'HTMLElement', 'localStorage', 'sessionStorage', 'location']
/**
* 添加文件
*/
function addFile(compilation, filename, content) {
compilation.assets[filename] = {
source: () => content,
size: () => Buffer.from(content).length,
}
}
/**
* chunk 头尾追加内容
*/
function wrapChunks(compilation, chunks) {
chunks.forEach(chunk => {
chunk.files.forEach(fileName => {
if (ModuleFilenameHelpers.matchObject({test: /\.js$/}, fileName)) {
const headerContent = 'module.exports = function(window, document) {' + globalVars.map(item => `var ${item} = window.${item}`).join(';') + ';'
const footerContent = '}'
compilation.assets[fileName] = new ConcatSource(headerContent, compilation.assets[fileName], footerContent)
}
})
})
}
class MpPlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
const options = this.options
// 补充其他文件输出
compiler.hooks.emit.tapAsync(PluginName, (compilation, callback) => {
const entryNames = Array.from(compilation.entrypoints.keys())
// 合并页面配置
const globalConfig = options.global || {}
const pageConfigMap = options.pages || {}
entryNames.forEach(entryName => {
pageConfigMap[entryName] = Object.assign({}, globalConfig, pageConfigMap[entryName] || {})
})
for (const entryName of entryNames) {
const assets = {
js: [],
css: [],
}
const filePathMap = {}
const extRegex = /\.(css|js|wxss)(\?|$)/
const entryFiles = compilation.entrypoints.get(entryName).getFiles()
entryFiles.forEach(filePath => {
// 跳过非 css 和 js
const extMatch = extRegex.exec(filePath)
if (!extMatch) return
// 跳过已记录的
if (filePathMap[filePath]) return
filePathMap[filePath] = true
// 记录
let ext = extMatch[1]
ext = ext === 'wxss' ? 'css' : ext
assets[ext].push(filePath)
// 调整 css 内容
if (ext === 'css') {
compilation.assets[filePath] = new RawSource(adjustCss(compilation.assets[filePath].source()))
}
})
const addPageScroll = pageConfigMap[entryName] && pageConfigMap[entryName].windowScroll
const pageBackgroundColor = pageConfigMap[entryName] && pageConfigMap[entryName].backgroundColor
const reachBottom = pageConfigMap[entryName] && pageConfigMap[entryName].reachBottom
const reachBottomDistance = pageConfigMap[entryName] && pageConfigMap[entryName].reachBottomDistance
const pullDownRefresh = pageConfigMap[entryName] && pageConfigMap[entryName].pullDownRefresh
// 页面 js
let pageJsContent = pageJsTmpl.replace('/* INIT_FUNCTION */', `function init(window, document) {${assets.js.map(js => 'require(\'../../common/' + js + '\')(window, document)').join(';')}}`)
let pageScrollFunction = ''
let reachBottomFunction = ''
let pullDownRefreshFunction = ''
if (addPageScroll) {
pageScrollFunction = () => 'onPageScroll({ scrollTop }) {if (this.window) {this.window.document.documentElement.scrollTop = scrollTop || 0;this.window.$$trigger(\'scroll\');}},'
}
if (reachBottom) {
reachBottomFunction = () => 'onReachBottom() {if (this.window) {this.window.$$trigger(\'reachbottom\');}},'
}
if (pullDownRefresh) {
pullDownRefreshFunction = () => 'onPullDownRefresh() {if (this.window) {this.window.$$trigger(\'pulldownrefresh\');}},'
}
pageJsContent = pageJsContent.replace('/* PAGE_SCROLL_FUNCTION */', pageScrollFunction)
pageJsContent = pageJsContent.replace('/* REACH_BOTTOM_FUNCTION */', reachBottomFunction)
pageJsContent = pageJsContent.replace('/* PULL_DOWN_REFRESH_FUNCTION */', pullDownRefreshFunction)
addFile(compilation, `../pages/${entryName}/index.js`, pageJsContent)
// 页面 wxml
const pageWxmlContent = '<element wx:if="{{pageId}}" class="{{bodyClass}}" style="{{bodyStyle}}" data-private-node-id="e-body" data-private-page-id="{{pageId}}"></element>'
addFile(compilation, `../pages/${entryName}/index.wxml`, pageWxmlContent)
// 页面 wxss
let pageWxssContent = assets.css.map(css => `@import "../../common/${css}";`).join('\n')
if (pageBackgroundColor) pageWxssContent = `page { background-color: ${pageBackgroundColor}; }\n` + pageWxssContent
addFile(compilation, `../pages/${entryName}/index.wxss`, adjustCss(pageWxssContent))
// 页面 json
const pageJson = {
usingComponents: {
element: 'miniprogram-element'
}
}
if (reachBottom && typeof reachBottomDistance === 'number') {
pageJson.onReachBottomDistance = reachBottomDistance
}
if (pullDownRefresh) {
pageJson.enablePullDownRefresh = pullDownRefresh
}
const pageJsonContent = JSON.stringify(pageJson, null, '\t')
addFile(compilation, `../pages/${entryName}/index.json`, pageJsonContent)
}
const pages = entryNames.map(entryName => `pages/${entryName}/index`)
// 追加 webview 页面
if (options.redirect && (options.redirect.notFound === 'webview' || options.redirect.accessDenied === 'webview')) {
addFile(compilation, '../pages/webview/index.js', 'Page({data:{url:\'\'},onLoad: function(query){this.setData({url:decodeURIComponent(query.url)})}})')
addFile(compilation, '../pages/webview/index.wxml', '<web-view src="{{url}}"></web-view>')
addFile(compilation, '../pages/webview/index.wxss', '')
addFile(compilation, '../pages/webview/index.json', '{"usingComponents":{}}')
pages.push('pages/webview/index')
}
// app js
const appJsContent = 'App({})'
addFile(compilation, '../app.js', appJsContent)
// app wxss
const appWxssContent = appWxssTmpl
addFile(compilation, '../app.wxss', adjustCss(appWxssContent))
// app json
const userAppJson = options.appExtraConfig || {}
const appJsonContent = JSON.stringify({
pages,
window: options.app || {},
...userAppJson,
}, null, '\t')
addFile(compilation, '../app.json', appJsonContent)
// config js
const router = {}
if (options.router) {
// 处理 router
Object.keys(options.router).forEach(key => {
const pathObjList = []
let pathList = options.router[key]
pathList = Array.isArray(pathList) ? pathList : [pathList]
for (const pathItem of pathList) {
// 将每个 route 转成正则并进行序列化
if (!pathItem || typeof pathItem !== 'string') continue
const keys = []
const regexp = pathToRegexp(pathItem, keys)
const pattern = regexp.valueOf()
pathObjList.push({
regexp: pattern.source,
options: `${pattern.global ? 'g' : ''}${pattern.ignoreCase ? 'i' : ''}${pattern.multiline ? 'm' : ''}`,
})
}
router[key] = pathObjList
})
}
const configJsContent = 'module.exports = ' + JSON.stringify({
origin: options.origin || 'https://miniprogram.default',
entry: options.entry || '/',
router,
pages: pageConfigMap,
redirect: options.redirect || {},
optimization: options.optimization || {},
}, null, '\t')
addFile(compilation, '../config.js', configJsContent)
// project.config.json
const userPorjectConfigJson = options.projectConfig || {}
const projectConfigJson = Object.assign({}, projectConfigJsonTmpl)
const projectConfigJsonContent = JSON.stringify(_.merge(projectConfigJson, userPorjectConfigJson), null, '\t')
addFile(compilation, '../project.config.json', projectConfigJsonContent)
// package.json
const userPackageConfigJson = options.packageConfig || {}
const packageConfigJson = Object.assign({}, packageConfigJsonTmpl)
const packageConfigJsonContent = JSON.stringify(_.merge(packageConfigJson, userPackageConfigJson), null, '\t')
addFile(compilation, '../package.json', packageConfigJsonContent)
// sitemap.json
const userSitemapConfigJson = options.sitemapConfig
if (userSitemapConfigJson) {
const sitemapConfigJsonContent = JSON.stringify(userSitemapConfigJson, null, '\t')
addFile(compilation, '../sitemap.json', sitemapConfigJsonContent)
}
// node_modules
addFile(compilation, '../node_modules/.miniprogram', '')
callback()
})
// 处理头尾追加内容
compiler.hooks.compilation.tap(PluginName, compilation => {
if (this.afterOptimizations) {
compilation.hooks.afterOptimizeChunkAssets.tap(PluginName, chunks => {
wrapChunks(compilation, chunks)
})
} else {
compilation.hooks.optimizeChunkAssets.tapAsync(PluginName, (chunks, callback) => {
wrapChunks(compilation, chunks)
callback()
})
}
})
}
}
module.exports = MpPlugin

View File

@ -0,0 +1,415 @@
body {
display: block;
}
p {
display: block;
-webkit-margin-before: 1em;
-webkit-margin-after: 1em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
}
address, article, aside, div, footer, header, hgroup, main, nav, section {
display: block;
}
blockquote {
display: block;
-webkit-margin-before: 1em;
-webkit-margin-after: 1em;
-webkit-margin-start: 40px;
-webkit-margin-end: 40px;
}
figcaption {
display: block;
}
figure {
display: block;
-webkit-margin-before: 1em;
-webkit-margin-after: 1em;
-webkit-margin-start: 40px;
-webkit-margin-end: 40px;
}
q {
display: inline;
}
center {
display: block;
text-align: center;
}
hr {
display: block;
-webkit-margin-before: 0.5em;
-webkit-margin-after: 0.5em;
-webkit-margin-start: auto;
-webkit-margin-end: auto;
border-style: inset;
border-width: 1px;
}
video {
object-fit: contain;
-webkit-tap-highlight-color: transparent;
}
/* heading elements */
h1 {
display: block;
font-size: 2em;
-webkit-margin-before: 0.67em;
-webkit-margin-after: 0.67em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
font-weight: bold;
}
h2 {
display: block;
font-size: 1.5em;
-webkit-margin-before: 0.83em;
-webkit-margin-after: 0.83em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
font-weight: bold;
}
h3 {
display: block;
font-size: 1.17em;
-webkit-margin-before: 1em;
-webkit-margin-after: 1em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
font-weight: bold;
}
h4 {
display: block;
-webkit-margin-before: 1.33em;
-webkit-margin-after: 1.33em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
font-weight: bold;
}
h5 {
display: block;
font-size: .83em;
-webkit-margin-before: 1.67em;
-webkit-margin-after: 1.67em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
font-weight: bold;
}
h6 {
display: block;
font-size: .67em;
-webkit-margin-before: 2.33em;
-webkit-margin-after: 2.33em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
font-weight: bold;
}
/* tables */
table {
display: table;
border-collapse: separate;
border-spacing: 2px;
border-color: gray;
}
thead {
display: table-header-group;
vertical-align: middle;
border-color: inherit;
}
tbody {
display: table-row-group;
vertical-align: middle;
border-color: inherit;
}
tfoot {
display: table-footer-group;
vertical-align: middle;
border-color: inherit;
}
/* for tables without table section elements (can happen with XHTML or dynamically created tables) */
table > tr {
vertical-align: middle;
}
col {
display: table-column;
}
colgroup {
display: table-column-group;
}
tr {
display: table-row;
vertical-align: inherit;
border-color: inherit;
}
td, th {
display: table-cell;
vertical-align: inherit;
}
th {
font-weight: bold;
}
caption {
display: table-caption;
text-align: center;
}
/* lists */
ul, menu, dir {
display: block;
list-style-type: disc;
-webkit-margin-before: 1em;
-webkit-margin-after: 1em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
-webkit-padding-start: 40px;
}
ol {
display: block;
list-style-type: decimal;
-webkit-margin-before: 1em;
-webkit-margin-after: 1em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
-webkit-padding-start: 40px;
}
li {
display: list-item;
text-align: -webkit-match-parent;
}
dd {
display: block;
-webkit-margin-start: 40px;
}
dl {
display: block;
-webkit-margin-before: 1em;
-webkit-margin-after: 1em;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
}
dt {
display: block;
}
/* form elements */
form {
display: block;
margin-top: 0em;
}
label {
cursor: default;
}
legend {
display: block;
-webkit-padding-start: 2px;
-webkit-padding-end: 2px;
border: none;
}
fieldset {
display: block;
-webkit-margin-start: 2px;
-webkit-margin-end: 2px;
-webkit-padding-before: 0.35em;
-webkit-padding-start: 0.75em;
-webkit-padding-end: 0.75em;
-webkit-padding-after: 0.625em;
border: 2px groove ThreeDFace;
min-width: min-content;
}
/* Form controls don't go vertical. */
input, textarea, keygen, select, button, progress {
-webkit-writing-mode: horizontal-tb !important;
}
input, textarea, keygen, select, button {
margin: 0em;
font: -webkit-small-control;
color: initial;
letter-spacing: normal;
word-spacing: normal;
line-height: normal;
text-transform: none;
text-indent: 0;
text-shadow: none;
display: inline-block;
text-align: start;
}
datalist {
display: none;
}
keygen, select {
border-radius: 5px;
}
area, param {
display: none;
}
select {
box-sizing: border-box;
letter-spacing: normal;
word-spacing: normal;
line-height: normal;
border: 1px solid #4c4c4c;
/* We want to be as close to background:transparent as possible without actually being transparent */
background-color: rgba(255, 255, 255, 0.01);
font: 11px Helvetica;
padding: 0 0.4em 0 0.4em;
border: 1px solid;
color: text;
background-color: -apple-system-control-background;
color: black;
background-color: white;
align-items: center;
white-space: pre;
-webkit-rtl-ordering: logical;
cursor: default;
}
optgroup {
font-weight: bolder;
}
option {
font-weight: normal;
}
output {
display: inline;
}
/* inline elements */
u, ins {
text-decoration: underline;
}
strong, b {
font-weight: bold;
}
i, cite, em, var, address, dfn {
font-style: italic;
}
tt, code, kbd, samp {
font-family: monospace;
}
pre {
display: block;
font-family: monospace;
white-space: pre;
margin: 1em 0;
}
mark {
background-color: yellow;
color: black;
}
big {
font-size: larger;
}
small {
font-size: smaller;
}
s, strike, del {
text-decoration: line-through;
}
sub {
vertical-align: sub;
font-size: smaller;
}
sup {
vertical-align: super;
font-size: smaller;
}
/* other elements */
iframe {
border: 2px inset;
}
details {
display: block;
}
summary {
display: block;
}
template {
display: none;
}
bdi, output {
unicode-bidi: isolate;
}
bdo {
unicode-bidi: bidi-override;
}
img {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/**
* 兼容标签样式
*/
img {
display: inline-block;
}
br {
display: inline-block;
}
a, abbr, b, button, code, i, label, small, span, strong, time {
display: inline;
}

View File

@ -0,0 +1,9 @@
{
"name": "miniprogram-project",
"version": "0.0.1",
"description": "miniprogram project",
"dependencies": {
"miniprogram-render": "latest",
"miniprogram-element": "latest"
}
}

View File

@ -0,0 +1,163 @@
const mp = require('miniprogram-render')
const config = require('../../config')
/* INIT_FUNCTION */
/**
* 处理一些特殊的页面
*/
function dealWithPage(evt, window, value) {
const type = evt.type
let url = evt.url
if (value === 'webview') {
// 补全 url
url = mp.$$adapter.tool.completeURL(url, window.location.origin)
const options = {url: `/pages/webview/index?url=${encodeURIComponent(url)}`}
if (type === 'jump') wx.redirectTo(options)
else if (type === 'open') wx.navigateTo(options)
} else if (value === 'error') {
console.error(`page not found: ${evt.url}`)
} else if (value !== 'none') {
const targeturl = `${window.location.origin}/redirect?url=${encodeURIComponent(url)}`
const options = {url: `/pages/${value}/index?type=${type}&targeturl=${encodeURIComponent(targeturl)}`}
if (type === 'jump') wx.redirectTo(options)
else if (type === 'open') wx.navigateTo(options)
}
}
Page({
data: {
pageId: '',
bodyClass: 'h5-body miniprogram-root',
bodyStyle: '',
},
onLoad(query) {
const pageName = mp.$$adapter.tool.getPageName(this.route)
const pageConfig = this.pageConfig = config.pages[pageName] || {}
if (pageConfig.loadingText) {
wx.showLoading({
title: pageConfig.loadingText,
mask: true,
})
}
const mpRes = mp.createPage(this.route, config)
this.pageId = mpRes.pageId
this.window = mpRes.window
this.document = mpRes.document
this.query = query
init(this.window, this.document)
// 处理跳转页面不存在的情况
if (config.redirect && config.redirect.notFound) {
this.window.addEventListener('pagenotfound', evt => {
dealWithPage(evt, mpRes.window, config.redirect.notFound)
})
}
// 处理跳转受限制页面的情况
if (config.redirect && config.redirect.accessDenied) {
this.window.addEventListener('pageaccessdenied', evt => {
dealWithPage(evt, mpRes.window, config.redirect.accessDenied)
})
}
if (query.type === 'open' || query.type === 'jump' || query.type === 'share') {
// 处理页面参数,只有当页面是其他页面打开或跳转时才处理
this.window.$$miniprogram.init(query.targeturl ? decodeURIComponent(query.targeturl) : null)
if (query.search) this.window.location.search = decodeURIComponent(query.search)
if (query.hash) this.window.location.hash = decodeURIComponent(query.hash)
} else {
this.window.$$miniprogram.init()
}
// 处理分享显示
if (!pageConfig.share) {
wx.hideShareMenu()
}
// 处理 body 更新
this.document.documentElement.addEventListener('$$childNodesUpdate', () => {
const domNode = this.document.body
const data = {
bodyClass: `${domNode.className || ''} h5-body miniprogram-root`, // 增加默认 class
bodyStyle: domNode.style.cssText || ''
}
if (data.bodyClass !== this.data.bodyClass || data.bodyStyle !== this.data.bodyStyle) {
this.setData(data)
}
})
// 处理 selectorQuery 获取
this.window.$$createSelectorQuery = () => wx.createSelectorQuery().in(this)
// 处理 intersectionObserver 获取
this.window.$$createIntersectionObserver = options => wx.createIntersectionObserver(this, options)
this.setData({
pageId: this.pageId
})
this.app = this.window.createApp()
},
onShow() {
// 方便调试
global.$$runtime = {
window: this.window,
document: this.document,
}
},
onReady() {
if (this.pageConfig.loadingText) wx.hideLoading()
},
onHide() {
global.$$runtime = null
},
onUnload() {
this.window.$$trigger('beforeunload')
this.app && this.app.$destroy && this.app.$destroy()
this.document.body.$$recycle() // 回收 dom 节点
mp.destroyPage(this.pageId)
global.$$runtime = null
this.pageConfig = null
this.pageId = null
this.window = null
this.document = null
this.app = null
this.query = null
},
onShareAppMessage(data) {
if (this.window.onShareAppMessage) {
const shareOptions = this.window.onShareAppMessage(data)
const query = Object.assign({}, this.query || {})
if (shareOptions.path) {
query.targeturl = encodeURIComponent(shareOptions.path)
} else {
// 组装当前页面路径
const location = this.window.location
query.targeturl = encodeURIComponent(location.href)
query.search = encodeURIComponent(location.search)
query.hash = encodeURIComponent(location.hash)
}
query.type = 'share'
const queryString = Object.keys(query).map(key => `${key}=${query[key] || ''}`).join('&')
const currentPagePath = `${this.route}?${queryString}`
shareOptions.path = currentPagePath
return shareOptions
}
},
/* PAGE_SCROLL_FUNCTION */
/* REACH_BOTTOM_FUNCTION */
/* PULL_DOWN_REFRESH_FUNCTION */
})

View File

@ -0,0 +1,60 @@
{
"description": "项目配置文件",
"packOptions": {
"ignore": []
},
"setting": {
"urlCheck": false,
"es6": true,
"enhance": true,
"postcss": true,
"minified": true,
"newFeature": true,
"nodeModules": true,
"autoAudits": false,
"uglifyFileName": true,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"babelSetting": {
"ignore": [
"common/*"
],
"disablePlugins": [],
"outputPath": ""
}
},
"compileType": "miniprogram",
"libVersion": "2.7.4",
"appid": "",
"projectname": "kbone-project",
"debugOptions": {
"hidedInDevtools": [
{
"type": "file",
"value": "WAService.js"
}
]
},
"isGameTourist": false,
"simulatorType": "wechat",
"simulatorPluginLibVersion": {},
"condition": {
"search": {
"current": -1,
"list": []
},
"conversation": {
"current": -1,
"list": []
},
"game": {
"currentL": -1,
"list": []
},
"miniprogram": {
"current": -1,
"list": []
}
}
}

View File

@ -0,0 +1,69 @@
const postcss = require('postcss')
const tagList = require('./tag-list')
const replaceRegexp = new RegExp(`(\\W|\\b)(${['html', ...tagList].join('|')})(\\W|\\b)`, 'ig')
const prefixRegexp = /[a-zA-Z0-9:.#_-]/
const suffixRegexp = /[a-zA-Z0-9_-]/
/**
* 替换标签名
*/
const replaceTagNamePlugin = postcss.plugin('replaceTagName', () => root => {
root.walk(child => {
if (child.type === 'rule') {
const selectors = []
child.selectors.forEach(selector => {
// 处理标签名选择器
selector = selector.replace(replaceRegexp, (all, $1, tagName, $2, offset, string) => {
// 非标签选择器,调整 \b 匹配的情况
let start = $1
let end = $2
if (!start) start = string[offset - 1] || ''
if (!end) end = string[offset + all.length] || ''
if (prefixRegexp.test(start) || suffixRegexp.test(end)) {
// 非标签选择器
return all
}
tagName = tagName.toLowerCase()
if (tagName === 'html') {
// 页面单独处理
return `${$1}page${$2}`
} else if (tagName) {
// 其他用原本的标签名
return `${$1}.h5-${tagName}${$2}`
} else {
return all
}
})
// 处理 * 号选择器
selector = selector.replace(/(.*)\*(.*)/g, (all, $1, $2) => {
if ($2[0] === '=') return all
tagList.forEach(tagName => selectors.push(`${$1}.h5-${tagName}${$2}`))
selectors.push(`${$1}page${$2}`)
return ''
})
if (selector.trim()) selectors.push(selector)
})
child.selectors = selectors
}
})
})
module.exports = function(code) {
code = postcss([replaceTagNamePlugin]).process(code, {
from: undefined, // 主要是不想看到那个 warning
map: null,
})
return code.css
}

View File

@ -0,0 +1,2 @@
// css * 号选择器支持的标签列表
module.exports = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'big', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'nav', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select', 'small', 'source', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tt', 'tr', 'track', 'u', 'ul', 'video', 'wbr', 'strike', 'details', 'summary']

View File

@ -0,0 +1,38 @@
/**
* 深合并对象
*/
function merge(to, from) {
if (typeof to !== 'object' || typeof from !== 'object') return to
const fromKeys = Object.keys(from)
for (const key of fromKeys) {
const fromValue = from[key]
const fromType = typeof fromValue
const isFromArray = +Array.isArray(fromValue)
const toValue = to[key]
const toType = typeof toValue
const isToArray = +Array.isArray(toValue)
// eslint-disable-next-line no-bitwise
if (fromType !== toType || (isFromArray ^ isToArray)) {
// 不同类型
to[key] = fromValue
} else {
// 相同类型
// eslint-disable-next-line no-lonely-if
if (isFromArray) {
fromValue.forEach(item => toValue.push(item))
} else if (fromType === 'object') {
to[key] = merge(toValue, fromValue)
} else {
to[key] = fromValue
}
}
}
return to
}
module.exports = {
merge,
}