diff --git a/packages/mp-webpack-plugin2/.eslintrc.js b/packages/mp-webpack-plugin2/.eslintrc.js new file mode 100644 index 000000000..ace086c2f --- /dev/null +++ b/packages/mp-webpack-plugin2/.eslintrc.js @@ -0,0 +1,13 @@ +const path = require('path') + +module.exports = { + 'extends': [ + path.resolve(__dirname, '../../.eslintrc.js'), + ], + 'rules': { + 'import/no-unresolved': 'off', + }, + 'globals': { + 'init': true, + }, +} diff --git a/packages/mp-webpack-plugin2/CHANGELOG.md b/packages/mp-webpack-plugin2/CHANGELOG.md new file mode 100644 index 000000000..17025001a --- /dev/null +++ b/packages/mp-webpack-plugin2/CHANGELOG.md @@ -0,0 +1,3 @@ +# 更新日志 + +## next version diff --git a/packages/mp-webpack-plugin2/README.md b/packages/mp-webpack-plugin2/README.md new file mode 100644 index 000000000..18e41192e --- /dev/null +++ b/packages/mp-webpack-plugin2/README.md @@ -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)。 diff --git a/packages/mp-webpack-plugin2/package.json b/packages/mp-webpack-plugin2/package.json new file mode 100644 index 000000000..7d1f7494d --- /dev/null +++ b/packages/mp-webpack-plugin2/package.json @@ -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" +} diff --git a/packages/mp-webpack-plugin2/src/index.js b/packages/mp-webpack-plugin2/src/index.js new file mode 100755 index 000000000..203b1d2f0 --- /dev/null +++ b/packages/mp-webpack-plugin2/src/index.js @@ -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 = '' + 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', '') + 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 diff --git a/packages/mp-webpack-plugin2/src/tmpl/app.tmpl.wxss b/packages/mp-webpack-plugin2/src/tmpl/app.tmpl.wxss new file mode 100755 index 000000000..4dba38b0b --- /dev/null +++ b/packages/mp-webpack-plugin2/src/tmpl/app.tmpl.wxss @@ -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; +} diff --git a/packages/mp-webpack-plugin2/src/tmpl/package.tmpl.json b/packages/mp-webpack-plugin2/src/tmpl/package.tmpl.json new file mode 100644 index 000000000..06ef1a309 --- /dev/null +++ b/packages/mp-webpack-plugin2/src/tmpl/package.tmpl.json @@ -0,0 +1,9 @@ +{ + "name": "miniprogram-project", + "version": "0.0.1", + "description": "miniprogram project", + "dependencies": { + "miniprogram-render": "latest", + "miniprogram-element": "latest" + } +} diff --git a/packages/mp-webpack-plugin2/src/tmpl/page.tmpl.js b/packages/mp-webpack-plugin2/src/tmpl/page.tmpl.js new file mode 100755 index 000000000..68bba1033 --- /dev/null +++ b/packages/mp-webpack-plugin2/src/tmpl/page.tmpl.js @@ -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 */ +}) diff --git a/packages/mp-webpack-plugin2/src/tmpl/project.config.tmpl.json b/packages/mp-webpack-plugin2/src/tmpl/project.config.tmpl.json new file mode 100644 index 000000000..d699f41f2 --- /dev/null +++ b/packages/mp-webpack-plugin2/src/tmpl/project.config.tmpl.json @@ -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": [] + } + } +} \ No newline at end of file diff --git a/packages/mp-webpack-plugin2/src/tool/adjust-css.js b/packages/mp-webpack-plugin2/src/tool/adjust-css.js new file mode 100755 index 000000000..e20a5b202 --- /dev/null +++ b/packages/mp-webpack-plugin2/src/tool/adjust-css.js @@ -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 +} diff --git a/packages/mp-webpack-plugin2/src/tool/tag-list.js b/packages/mp-webpack-plugin2/src/tool/tag-list.js new file mode 100755 index 000000000..bba3a8b79 --- /dev/null +++ b/packages/mp-webpack-plugin2/src/tool/tag-list.js @@ -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'] diff --git a/packages/mp-webpack-plugin2/src/tool/utils.js b/packages/mp-webpack-plugin2/src/tool/utils.js new file mode 100644 index 000000000..8819a8868 --- /dev/null +++ b/packages/mp-webpack-plugin2/src/tool/utils.js @@ -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, +}