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,
+}