first commit

This commit is contained in:
2026-09-08 20:28:54 +08:00
commit 2ada8d3d5a
37380 changed files with 4886169 additions and 0 deletions
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+816
View File
@@ -0,0 +1,816 @@
import Vue from 'vue';
function parseData (data, vueComponentOptions) {
if (!data) {
return
}
vueComponentOptions.mpOptions.data = data;
}
function parseComponents (vueComponentOptions) {
vueComponentOptions.components = global.__wxVueOptions.components;
}
const _toString = Object.prototype.toString;
const hasOwnProperty = Object.prototype.hasOwnProperty;
function isFn (fn) {
return typeof fn === 'function'
}
function isPlainObject (obj) {
return _toString.call(obj) === '[object Object]'
}
function hasOwn (obj, key) {
return hasOwnProperty.call(obj, key)
}
function noop () {}
/**
* Create a cached version of a pure function.
*/
function cached (fn) {
const cache = Object.create(null);
return function cachedFn (str) {
const hit = cache[str];
return hit || (cache[str] = fn(str))
}
}
/**
* Camelize a hyphen-delimited string.
*/
const camelizeRE = /-(\w)/g;
const camelize = cached((str) => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
});
const SOURCE_KEY = '__data__';
const COMPONENT_LIFECYCLE = {
created: 'onServiceCreated',
attached: 'onServiceAttached',
ready: 'mounted',
moved: 'moved',
detached: 'destroyed'
};
const COMPONENT_LIFECYCLE_KEYS = Object.keys(COMPONENT_LIFECYCLE);
const PAGE_LIFETIMES = {
show: 'onPageShow',
hide: 'onPageHide',
resize: 'onPageResize'
};
const PAGE_LIFETIMES_KEYS = Object.keys(PAGE_LIFETIMES);
const PAGE_LIFECYCLE = [
'onLoad',
'onShow',
'onReady',
'onHide',
'onUnload',
'onPullDownRefresh',
'onReachBottom',
'onShareAppMessage',
'onPageScroll',
'onResize',
'onTabItemTap'
];
function parsePageMethods (mpComponentOptions, vueComponentOptions) {
const methods = Object.create(null);
Object.keys(mpComponentOptions).forEach(key => {
const value = mpComponentOptions[key];
if (isFn(value) && PAGE_LIFECYCLE.indexOf(key) === -1) {
methods[key] = value;
}
});
vueComponentOptions.methods = methods;
}
function parsePageLifecycle (mpComponentOptions, vueComponentOptions) {
Object.keys(mpComponentOptions).forEach(key => {
if (PAGE_LIFECYCLE.indexOf(key) !== -1) {
vueComponentOptions[key] = mpComponentOptions[key];
}
});
}
function parsePage (mpComponentOptions) {
const vueComponentOptions = {
mixins: [],
mpOptions: {}
};
parseComponents(vueComponentOptions);
parseData(mpComponentOptions.data, vueComponentOptions);
parsePageMethods(mpComponentOptions, vueComponentOptions);
parsePageLifecycle(mpComponentOptions, vueComponentOptions);
return vueComponentOptions
}
function parseProperties (properties, vueComponentOptions) {
if (!properties) {
return
}
vueComponentOptions.mpOptions.properties = properties;
}
function parseOptions (options, vueComponentOptions) {
if (!options) {
return
}
vueComponentOptions.mpOptions.options = options;
}
function parseMethods (methods, vueComponentOptions) {
if (!methods) {
return
}
if (methods.$emit) {
console.warn('Method "$emit" conflicts with an existing Vue instance method');
delete methods.$emit;
}
vueComponentOptions.methods = methods;
}
function parseLifecycle (mpComponentOptions, vueComponentOptions) {
COMPONENT_LIFECYCLE_KEYS.forEach(name => {
if (hasOwn(mpComponentOptions, name)) {
(vueComponentOptions[COMPONENT_LIFECYCLE[name]] || (vueComponentOptions[COMPONENT_LIFECYCLE[name]] = []))
.push(mpComponentOptions[name]);
}
});
}
const mpBehaviors = {
'wx://form-field': {},
'wx://component-export': {}
};
function callDefinitionFilter (mpComponentOptions) {
const {
behaviors,
definitionFilter
} = mpComponentOptions;
const behaviorDefinitionFilters = [];
if (Array.isArray(behaviors)) {
behaviors.forEach(behavior => {
behavior = typeof behavior === 'string' ? mpBehaviors[behavior] : behavior;
if (behavior.definitionFilter) {
behaviorDefinitionFilters.push(behavior.definitionFilter);
behavior.definitionFilter.call(null, mpComponentOptions, []);
}
});
}
if (isFn(definitionFilter)) {
return function (defFields) {
definitionFilter(defFields, behaviorDefinitionFilters);
}
}
}
function parseDefinitionFilter (mpComponentOptions, vueComponentOptions) {
callDefinitionFilter(mpComponentOptions);
}
function parseBehavior (behavior) {
const {
data,
methods,
behaviors,
properties
} = behavior;
const vueComponentOptions = {
watch: {},
mpOptions: {
mpObservers: []
}
};
parseData(data, vueComponentOptions);
parseMethods(methods, vueComponentOptions);
parseBehaviors(behaviors, vueComponentOptions);
parseProperties(properties, vueComponentOptions);
parseLifecycle(behavior, vueComponentOptions);
parseDefinitionFilter(behavior);
return vueComponentOptions
}
const BEHAVIORS = {
'wx://form-field': {
beforeCreate () {
const mpOptions = this.$options.mpOptions;
if (!mpOptions.properties) {
mpOptions.properties = Object.create(null);
}
const props = mpOptions.properties;
// TODO form submit,reset
if (!hasOwn(props, 'name')) {
props.name = {
type: String
};
}
if (!hasOwn(props, 'value')) {
props.value = {
type: String // 默认类型调整为 String,否则默认值为 null,导致一些自定义 input 显示不正确
};
}
}
}
};
function parseBehaviors (behaviors, vueComponentOptions) {
if (!behaviors) {
return
}
behaviors.forEach(behavior => {
if (typeof behavior === 'string') {
BEHAVIORS[behavior] && vueComponentOptions.mixins.push(BEHAVIORS[behavior]);
} else {
vueComponentOptions.mixins.push(parseBehavior(behavior));
}
});
}
function parseSinglePath (path) {
return path.split('.')
}
function parseMultiPaths (paths) {
return paths.split(',').map(path => parseSinglePath(path))
}
function parseObservers (observers, vueComponentOptions) {
if (!observers) {
return
}
const {
mpObservers
} = vueComponentOptions.mpOptions;
Object.keys(observers).forEach(path => {
mpObservers.push({
paths: parseMultiPaths(path),
observer: observers[path]
});
});
}
function relative (from, to) {
if (to.indexOf('/') === 0) {
from = '';
}
const fromArr = from.split('/');
const toArr = to.split('/');
fromArr.pop();
while (toArr.length) {
const part = toArr.shift();
if (part !== '' && part !== '.') {
if (part !== '..') {
fromArr.push(part);
} else {
fromArr.pop();
}
}
}
return fromArr.join('/')
}
function parseRelations (relations, vueComponentOptions) {
if (!relations) {
return
}
Object.keys(relations).forEach(name => {
const relation = relations[name];
relation.name = name;
relation.target = relation.target ? String(relation.target) : relative(global.__wxRoute, name);
});
vueComponentOptions.mpOptions.relations = relations;
}
function parseExternalClasses (externalClasses, vueComponentOptions) {
if (!externalClasses) {
return
}
if (!Array.isArray(externalClasses)) {
externalClasses = [externalClasses];
}
vueComponentOptions.mpOptions.externalClasses = externalClasses;
if (!vueComponentOptions.mpOptions.properties) {
vueComponentOptions.mpOptions.properties = Object.create(null);
}
externalClasses.forEach(externalClass => {
vueComponentOptions.mpOptions.properties[camelize(externalClass)] = {
type: String,
value: ''
};
});
}
function parseLifetimes (lifetimes, vueComponentOptions) {
if (!lifetimes) {
return
}
parseLifecycle(lifetimes, vueComponentOptions);
}
function parsePageLifetimes (pageLifetimes, vueComponentOptions) {
if (!pageLifetimes) {
return
}
PAGE_LIFETIMES_KEYS.forEach(key => {
const lifetimeFn = pageLifetimes[key];
isFn(lifetimeFn) && (vueComponentOptions[PAGE_LIFETIMES[key]] = lifetimeFn);
});
}
function parseComponent (mpComponentOptions) {
const {
data,
options,
methods,
behaviors,
lifetimes,
observers,
relations,
properties,
pageLifetimes,
externalClasses
} = mpComponentOptions;
const vueComponentOptions = {
mixins: [],
props: {},
watch: {},
mpOptions: {
mpObservers: []
}
};
parseComponents(vueComponentOptions);
parseData(data, vueComponentOptions);
parseOptions(options, vueComponentOptions);
parseMethods(methods, vueComponentOptions);
parseBehaviors(behaviors, vueComponentOptions);
parseLifetimes(lifetimes, vueComponentOptions);
parseObservers(observers, vueComponentOptions);
parseRelations(relations, vueComponentOptions);
parseProperties(properties, vueComponentOptions);
parsePageLifetimes(pageLifetimes, vueComponentOptions);
parseExternalClasses(externalClasses, vueComponentOptions);
parseLifecycle(mpComponentOptions, vueComponentOptions);
parseDefinitionFilter(mpComponentOptions);
return vueComponentOptions
}
function initRelationHandlers (type, handler, target, ctx) {
if (!handler) {
return
}
const name = `_$${type}Handlers`;
(ctx[name] || (ctx[name] = [])).push(function () {
handler.call(ctx, target);
});
}
function initLinkedHandlers (relation, target, ctx) {
const type = 'linked';
const name = relation.name;
const relationNodes = ctx._$relationNodes || (ctx._$relationNodes = Object.create(null));
(relationNodes[name] || (relationNodes[name] = [])).push(target);
initRelationHandlers(type, relation[type], target, ctx);
}
function initUnlinkedHandlers (relation, target, ctx) {
const type = 'unlinked';
initRelationHandlers(type, relation[type], target, ctx);
}
function findParentRelation (parentVm, target, type) {
const relations = parentVm &&
parentVm.$options.mpOptions &&
parentVm.$options.mpOptions.relations;
if (!relations) {
return []
}
const name = Object.keys(relations).find(name => {
const relation = relations[name];
return relation.target === target && relation.type === type
});
if (!name) {
return []
}
return [relations[name], parentVm]
}
function initParentRelation (vm, childRelation, match) {
const [parentRelation, parentVm] = match(vm, vm.$options.mpOptions.path);
if (!parentRelation) {
return
}
initLinkedHandlers(parentRelation, vm, parentVm);
initLinkedHandlers(childRelation, parentVm, vm);
initUnlinkedHandlers(parentRelation, vm, parentVm);
initUnlinkedHandlers(childRelation, parentVm, vm);
}
function initRelation (relation, vm) {
const type = relation.type;
if (type === 'parent') {
initParentRelation(vm, relation, function matchParent (vm, target) {
return findParentRelation(vm.$parent, target, 'child')
});
} else if (type === 'ancestor') {
initParentRelation(vm, relation, function matchAncestor (vm, target) {
let $parent = vm.$parent;
while ($parent) {
const ret = findParentRelation($parent, target, 'descendant');
if (ret.length) {
return ret
}
$parent = $parent.$parent;
}
return []
});
}
}
function initRelations (vm) {
const {
relations
} = vm.$options.mpOptions || {};
if (!relations) {
return
}
Object.keys(relations).forEach(name => {
initRelation(relations[name], vm);
});
}
function handleRelations (vm, type) {
// TODO 需要移除 relationNodes
const handlers = vm[`_$${type}Handlers`];
if (!handlers) {
return
}
handlers.forEach(handler => handler());
}
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function setDataByExprPath (exprPath, value, data) {
const keys = exprPath.replace(/\[(\d+?)\]/g, '.$1').split('.');
keys.reduce((obj, key, idx) => {
if (idx === keys.length - 1) {
obj[key] = value;
} else {
if (typeof obj[key] === 'undefined') {
obj[key] = {};
}
return obj[key]
}
}, data);
return keys.length === 1
}
function setData (data, callback) {
if (!isPlainObject(data)) {
return
}
Object.keys(data).forEach(key => {
if (setDataByExprPath(key, data[key], this.data)) {
!hasOwn(this, key) && proxy(this, SOURCE_KEY, key);
}
});
this.$forceUpdate();
isFn(callback) && this.$nextTick(callback);
}
/**
* https://github.com/swan-team/swan-js/blob/61e2a63f7aa576b5daafbe77fdfa7c65b977060c/src/utils/index.js
*/
const _toString$1 = Object.prototype.toString;
/**
* 深度assign的函数
* @param {Object} targetObject 要被拷贝的目标对象
* @param {Object} originObject 拷贝的源对象
* @return {Object} merge后的对象
*/
const deepAssign = (targetObject = {}, originObject) => {
const originType = _toString$1.call(originObject);
if (originType === '[object Array]') {
targetObject = originObject.slice(0);
return targetObject
} else if (originType === '[object Object]') {
for (const key in originObject) {
targetObject[key] = deepAssign(targetObject[key], originObject[key]);
}
return targetObject
} else if (originType === '[object Date]') {
return new Date(originObject.getTime())
} else if (originType === '[object RegExp]') {
const target = String(originObject);
const lastIndex = target.lastIndexOf('/');
return new RegExp(target.slice(1, lastIndex), target.slice(lastIndex + 1))
}
return originObject
};
/**
* 深度拷贝逻辑,不同于lodash等库,但是与微信一致
* @param {*} [originObj] 原对象
* @return {Object|Array} 拷贝结果
*/
const deepClone = originObj => {
return deepAssign(_toString$1.call(originObj) === '[object Array]' ? [] : {}, originObj)
};
const PROP_DEFAULT_VALUES = {
[String]: '',
[Number]: 0,
[Boolean]: false,
[Object]: null,
[Array]: [],
[null]: null
};
function getDefaultVal (propType) {
return PROP_DEFAULT_VALUES[propType]
}
function getPropertyVal (options) {
if (isPlainObject(options)) {
if (hasOwn(options, 'value')) {
return options.value
}
return getDefaultVal(options.type)
}
return getDefaultVal(options)
}
function getType (propOptions) {
return isPlainObject(propOptions) ? propOptions.type : propOptions
}
function validateProp (key, propsOptions, propsData, vm) {
let value = propsData[key];
if (value !== undefined) {
const propOptions = propsOptions[key];
const type = getType(propOptions);
value = formatVal(value, type);
const observer = propOptions && propOptions.observer;
if (observer) {
// 初始化时,异步触发 observer,否则 observer 中无法访问 methods 或其他
setTimeout(function () {
observe(observer, vm, value);
}, 4);
}
return value
}
return getPropertyVal(propsOptions[key])
}
function formatVal (val, type) {
if (type === Boolean) {
return !!val
} else if (type === String) {
return String(val)
}
return val
}
function observe (observer, vm, newVal, oldVal) {
try {
if (typeof observer === 'function') {
observer.call(vm, newVal, oldVal);
} else if (typeof observer === 'string' &&
typeof vm[observer] === 'function'
) {
vm[observer](newVal, oldVal);
}
} catch (err) {
console.error(`execute observer ${observer} callback fail! err: ${err}`);
}
}
function initProperties (vm, instanceData) {
const properties = vm.$options.mpOptions.properties;
if (!properties) {
return
}
const propsData = deepClone(vm.$options.propsData) || {};
for (const key in properties) {
const observer = isPlainObject(properties[key]) ? properties[key].observer : false;
let value = validateProp(key, properties, propsData, vm);
Object.defineProperty(instanceData, key, {
enumerable: true,
configurable: true,
get () {
return value
},
set (newVal) {
const oldVal = value;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// TODO 临时方案,clone array
value = Array.isArray(newVal) ? newVal.slice(0) : newVal;
if (observer) {
observe(observer, vm, newVal, oldVal);
}
// 触发渲染
vm.$forceUpdate();
}
});
}
}
function updateProperties (vm) {
const properties = vm.$options.mpOptions && vm.$options.mpOptions.properties;
const propsData = vm.$options.propsData;
if (propsData && properties) {
Object.keys(properties).forEach(key => {
if (hasOwn(propsData, key)) {
vm[key] = formatVal(propsData[key], getType(properties[key]));
}
});
}
}
function initState (vm) {
const instanceData = JSON.parse(JSON.stringify(vm.$options.mpOptions.data || {}));
vm[SOURCE_KEY] = instanceData;
const propertyDefinition = {
get () {
return vm[SOURCE_KEY]
},
set (value) {
vm[SOURCE_KEY] = value;
}
};
Object.defineProperties(vm, {
data: propertyDefinition,
properties: propertyDefinition
});
vm.setData = setData;
initProperties(vm, instanceData);
Object.keys(instanceData).forEach(key => {
proxy(vm, SOURCE_KEY, key);
});
}
function initMethods (vm) {
const oldEmit = vm.$emit;
vm.triggerEvent = (eventName, detail, options) => {
const target = {
dataset: vm.$el.dataset
};
const event = {
target,
currentTarget: target,
detail,
preventDefault: noop,
stopPropagation: noop
};
oldEmit.call(vm, eventName, event);
};
// 主要是Vant 自己封装了 $emit,放到 methods 中会触发 Vue 的警告,索性,框架直接重写该方法
vm.$emit = (...args) => {
vm.triggerEvent(...args);
};
vm.getRelationNodes = (relationKey) => {
// 需要过滤已被销毁的vm
/* eslint-disable no-mixed-operators */
return (vm._$relationNodes && vm._$relationNodes[relationKey] || []).filter(vm => !vm._isDestroyed)
};
vm._$updateProperties = updateProperties;
}
function handleObservers (vm) {
const watch = vm.$options.watch;
if (!watch) {
return
}
Object.keys(watch).forEach(name => {
const observer = watch[name];
if (observer.mounted) {
const val = vm[name];
let handler = observer.handler;
if (typeof handler === 'string') {
handler = vm[handler];
}
handler && handler.call(vm, val, val);
}
});
}
var polyfill = {
beforeCreate () {
// 取消 development 时的 Proxy,避免小程序组件模板中使用尚未定义的属性告警
this._renderProxy = this;
this._$self = this;
this._$noop = noop;
},
created () { // properties 中可能会访问 methods,故需要在 created 中初始化
initState(this);
initMethods(this);
initRelations(this);
},
mounted () {
handleObservers(this);
},
destroyed () {
handleRelations(this, 'unlinked');
}
};
global.__wxRoute = '';
global.__wxComponents = Object.create(null);
global.__wxVueOptions = Object.create(null);
function Page (options) {
const pageOptions = parsePage(options);
pageOptions.mixins.unshift(polyfill);
pageOptions.mpOptions.path = global.__wxRoute;
global.__wxComponents[global.__wxRoute] = pageOptions;
}
function initRelationsHandler (vueComponentOptions) {
// linked 需要在当前组件 attached 之后再执行
if (!vueComponentOptions.onServiceAttached) {
vueComponentOptions.onServiceAttached = [];
}
vueComponentOptions.onServiceAttached.push(function onServiceAttached () {
handleRelations(this, 'linked');
});
}
function Component (options) {
const componentOptions = parseComponent(options);
componentOptions.mixins.unshift(polyfill);
componentOptions.mpOptions.path = global.__wxRoute;
initRelationsHandler(componentOptions);
global.__wxComponents[global.__wxRoute] = componentOptions;
}
function Behavior (options) {
return options
}
const nextTick = Vue.nextTick;
var index = uni.__$wx__;
export default index;
export { Behavior, Component, Page, nextTick };
+55
View File
@@ -0,0 +1,55 @@
const objectKeys = [
'qy',
'env',
'error',
'version',
'lanDebug',
'cloud',
'serviceMarket',
'router',
'worklet',
'__webpack_require_UNI_MP_PLUGIN__'
]
const singlePageDisableKey = [
'lanDebug',
'router',
'worklet'
]
const target = typeof globalThis !== 'undefined' ? globalThis : (function () {
return this
})()
const key = ['w', 'x'].join('')
const oldWx = target[key]
const launchOption = oldWx.getLaunchOptionsSync ? oldWx.getLaunchOptionsSync() : null
function isWxKey (key) {
if (launchOption && launchOption.scene === 1154 && singlePageDisableKey.includes(key)) {
return false
}
return objectKeys.indexOf(key) > -1 || typeof oldWx[key] === 'function'
}
function initWx () {
const newWx = {}
for (const key in oldWx) {
if (isWxKey(key)) {
// TODO wrapper function
newWx[key] = oldWx[key]
}
}
return newWx
}
target[key] = initWx()
if (!target[key].canIUse('getAppBaseInfo')) {
target[key].getAppBaseInfo = target[key].getSystemInfoSync
}
if (!target[key].canIUse('getWindowInfo')) {
target[key].getWindowInfo = target[key].getSystemInfoSync
}
if (!target[key].canIUse('getDeviceInfo')) {
target[key].getDeviceInfo = target[key].getSystemInfoSync
}
export default target[key]
+19
View File
@@ -0,0 +1,19 @@
/**
* wxs getRegExp
*/
function getRegExp () {
var args = Array.prototype.slice.call(arguments);
args.unshift(RegExp);
return new (Function.prototype.bind.apply(RegExp, args))()
}
/**
* wxs getDate
*/
function getDate () {
var args = Array.prototype.slice.call(arguments);
args.unshift(Date);
return new (Function.prototype.bind.apply(Date, args))()
}
export { getDate, getRegExp };
@@ -0,0 +1,173 @@
const path = require('path');
const fs = require('fs-extra');
const htmlparser2 = require('htmlparser2');
const { MpComponentFileExtension } = require('./constant');
const { transformScript, resolveToContext } = require('./utils.js');
const { parse } = require('@babel/parser');
const { default: traverse } = require('@babel/traverse');
class CollectDependency {
constructor (context, readFileSync, existsSync, addExtension) {
this.context = context;
this.existsSync = existsSync || fs.existsSync.bind(fs);
this.readFileSync = readFileSync || fs.readFileSync.bind(fs);
this.addExtension = addExtension;
}
getWxCssDeps (file) {
const deps = [];
const dirName = path.dirname(file);
let content = this.readFileSync(file, 'utf-8');
if (content instanceof Buffer) {
content = content.toString('utf-8');
}
const importRegExp = /@import\s*['"](.+)['"];*/g;
let matched;
while ((matched = importRegExp.exec(content)) !== null) {
if (!matched[1]) {
continue;
}
const wxssFile = resolveToContext(dirName, matched[1], this.context);
if (this.existsSync(wxssFile)) {
deps.push(wxssFile);
}
}
return deps;
};
getWxHtmlDeps (file) {
const deps = [];
const dirName = path.dirname(file);
let content = this.readFileSync(file, 'utf-8');
if (content instanceof Buffer) {
content = content.toString('utf-8');
}
// https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/import.html
// WXML 提供两种文件引用方式import和include。
const targetTags = ['import', 'include', 'wxs'];
const existsSync = this.existsSync;
const htmlParser = new htmlparser2.Parser({
onopentag (name, attribs = {}) {
if (!targetTags.includes(name)) {
return;
}
const { src } = attribs;
if (!src) {
return;
}
const wxmlFile = resolveToContext(dirName, src, this.context);
console.log(wxmlFile);
if (existsSync(wxmlFile)) {
deps.push(wxmlFile);
}
},
});
htmlParser.write(content);
htmlParser.end();
return deps;
}
getJsonDeps (file) {
const deps = [];
const dirName = path.dirname(file);
let fileContent = this.readFileSync(file);
if (fileContent && fileContent instanceof Buffer) {
fileContent = fileContent.toString('utf-8');
}
if (!fileContent || !(fileContent.trim())) {
return [];
}
fileContent = JSON.parse(fileContent);
const usingComponents = fileContent.usingComponents;
if (usingComponents && typeof usingComponents === 'object') {
Object.values(usingComponents).forEach((component) => {
component = resolveToContext(dirName, component, this.context);
// 每个组件都需要判断 js/json/wxml/wxss 文件是否存在
MpComponentFileExtension.forEach((ext) => {
const file = this.addExtension(component, ext);
if (this.existsSync(file)) {
deps.push(file);
}
});
});
}
return deps;
}
getJsDeps (file) {
const deps = [];
const dirName = path.dirname(file);
// 读取 js 文件内容
let content = this.readFileSync(file, 'utf-8');
if (content instanceof Buffer) {
content = content.toString('utf-8');
}
// 将代码转化为 AST
const ast = parse(content, { sourceType: 'module', plugins: ['exportDefaultFrom'] });
// 遍历 AST
traverse(ast, {
ImportDeclaration: ({ node }) => {
// 获取 import from 地址
const { value } = node.source;
const jsFile = transformScript(dirName, value, this.existsSync);
if (jsFile) {
deps.push(jsFile);
}
},
Property: (nodePath) => {
const parentPath = nodePath.parentPath;
const callee = parentPath && parentPath.parent && parentPath.parent.callee;
if (!callee || callee.name !== 'Component') {
return;
}
const relationsNodes = nodePath.container.filter(item => item.key.name === 'relations');
if (!relationsNodes.length) {
return;
}
const relationsNode = relationsNodes[0];
const propertyNodes = relationsNode.value.properties || [];
let allRelationsComponents = propertyNodes.map(filePathNode => {
if (!filePathNode.key.value) {
return '';
}
return path.resolve(dirName, filePathNode.key.value);
});
allRelationsComponents = allRelationsComponents.filter(item => item);
allRelationsComponents.forEach(component => {
MpComponentFileExtension.forEach((ext) => {
const file = this.addExtension(component, ext);
if (this.existsSync(file)) {
deps.push(file);
}
});
});
},
ExportNamedDeclaration: ({ node }) => {
if (!node.source) {
return;
}
// 获取 export from 地址
const { value } = node.source;
const jsFile = transformScript(dirName, value, this.existsSync);
if (jsFile) {
deps.push(jsFile);
}
},
CallExpression: ({ node }) => {
if ((node.callee.name && node.callee.name === 'require') && node.arguments.length >= 1) {
// 获取 require 地址
const [{ value }] = node.arguments;
const jsFile = transformScript(dirName, value, this.existsSync);
if (jsFile) {
deps.push(jsFile);
}
}
},
});
return deps;
}
}
module.exports = CollectDependency;
@@ -0,0 +1,11 @@
const WxMpFileExtension = {
js: 'js',
json: 'json',
wxml: 'wxml',
wxss: 'wxss',
};
module.exports = {
MpComponentFileExtension: Object.values(WxMpFileExtension),
WxMpFileExtension: Object.assign({}, WxMpFileExtension, { 'wxs': 'wxs' }),
};
@@ -0,0 +1,80 @@
const fs = require('fs-extra');
const path = require('path');
const { addExtension } = require('./utils.js');
const { MpComponentFileExtension, WxMpFileExtension } = require('./constant.js');
const CollectDependency = require('./collect-dependency.js');
const extToCollectMethodName = {
[WxMpFileExtension.js]: 'getJsDeps',
[WxMpFileExtension.wxs]: 'getJsDeps',
[WxMpFileExtension.wxss]: 'getWxCssDeps',
[WxMpFileExtension.wxml]: 'getWxHtmlDeps',
[WxMpFileExtension.json]: 'getJsonDeps',
};
class Depend {
constructor (context, readFileSync, existsSync) {
this.existsSync = existsSync || fs.existsSync.bind(fs);
this.readFileSync = readFileSync || fs.readFileSync.bind(fs);
this.componentLogicFiles = new Set();
this.files = new Set();
this.context = context;
this.allComponents = new Set();
this.collectDependency = new CollectDependency(context, this.readFileSync, this.existsSync, this.addExtension.bind(this));
}
addExtension (filePath, ext = '') {
if (ext === WxMpFileExtension.json) {
this.allComponents.add(filePath);
}
return `${filePath}.${ext}`;
}
// 将文件添加到树中
addToTree (filePath) {
filePath = path.resolve(filePath);
if (this.files.has(filePath)) {
return;
}
this.files.add(filePath);
const deps = this.getDeps(filePath) || [];
deps.forEach(dep => this.addToTree(dep));
}
getDeps (filePath) {
const extension = path.extname(filePath).slice(1);
const collectMethod = extToCollectMethodName[extension];
if (!collectMethod) {
return [];
}
const files = this.collectDependency[collectMethod](filePath);
const logicFiles = files.filter(filePath => filePath.endsWith(WxMpFileExtension.js));
logicFiles.forEach(logicFile => this.componentLogicFiles.add(logicFile));
return files;
}
getDepsByPageOrComponentPath (absPath) {
MpComponentFileExtension.forEach(ext => {
const filePath = this.addExtension(absPath, ext);
if (this.existsSync(filePath)) {
if (ext === WxMpFileExtension.js) {
this.componentLogicFiles.add(filePath);
}
this.addToTree(filePath);
}
});
}
getDepsByComponents (components) {
components.forEach(page => {
// 获取绝对地址
const absPath = path.join(this.context, page);
this.getDepsByPageOrComponentPath(absPath);
});
return this.files;
}
}
module.exports = Depend;
@@ -0,0 +1,44 @@
const path = require('path');
const fs = require('fs-extra');
module.exports = {
resolveToContext(dirName, relativePath, context) {
if (relativePath.startsWith('/')) {
return `${context}${relativePath}`;
}
return path.resolve(dirName, relativePath);
},
// 获取某个路径的脚本文件
transformScript: function (url, value, existsSync = fs.existsSync) {
url = `${url}/${value}`;
const ext = path.extname(url);
// 如果存在后缀,表示当前已经是一个文件
const exts = ['.js', '.wxs'];
if (exts.includes(ext) && existsSync(url)) {
return url;
}
// a/b/c => a/b/c.js
const jsFile = url + '.js';
if (existsSync(jsFile)) {
return jsFile;
}
// a/b/c => a/b/c.js
const wxsFile = url + '.wxs';
if (existsSync(wxsFile)) {
return wxsFile;
}
// a/b/c => a/b/c/index.js
const jsIndexFile = path.join(url, 'index.js');
if (existsSync(jsIndexFile)) {
return jsIndexFile;
}
const wxsIndexFile = path.join(url, 'index.wxs');
if (existsSync(wxsIndexFile)) {
return wxsIndexFile;
}
return null;
}
};
+37
View File
@@ -0,0 +1,37 @@
const AddShareAbilityToRuntimePlugin = require('./independent-plugins/add-share-ability-to-runtime-plugin')
const GenerateIndepndentEntryPlugin = require('./independent-plugins/generate-indepndent-entry-plugin')
const InjectEntryJsToIndependentPlugin = require('./independent-plugins/inject-entry-to-independent-plugin')
const InjectMainCssToIndependentCssPlugin = require('./independent-plugins/inject-main-css-to-independent-plugin')
const RunDefaultAppPlugin = require('./independent-plugins/run-default-app-plugin')
const SplitIndependentChunksPlugin = require('./independent-plugins/split-independent-chunks-plugin')
const ModifyUniAppWebpackConfigPlugin = require('./independent-plugins/modify-uniapp-webpack-config-Plugin')
const AddWxMpRuntimePlugin = require('./independent-plugins/add-weixin-mp-runtime-plugin')
const AppInterceptorPlugin = require('./independent-plugins/app-interceptor-plugin')
module.exports = function createIndependentPlugins () {
const manifestConfig = process.UNI_MANIFEST
const weixinConfig = manifestConfig['mp-weixin'] || {}
const independentSwitch = !!weixinConfig.independent
if (!independentSwitch) return []
// 支持构造微信小程序的独立分包
const independentPlugins = [
new SplitIndependentChunksPlugin(),
new ModifyUniAppWebpackConfigPlugin(), // 修改 webpack配置
new AddShareAbilityToRuntimePlugin(), // 保证独立分包和主包使用的相同的runtime.js
new GenerateIndepndentEntryPlugin(), // 生成独立分包执行入口文件(代替app.js
new InjectEntryJsToIndependentPlugin(), // 为独立分包注入执行入口
new RunDefaultAppPlugin(), // 确保app.js中的App()被执行一次
// 独立分包中 App,getApp 调用拦截
new AddWxMpRuntimePlugin(),
new AppInterceptorPlugin()
]
const insertAppCssToIndependentSwitch = !!weixinConfig.insertAppCssToIndependent
if (insertAppCssToIndependentSwitch) {
// 需要在 cacheSet 后面
independentPlugins.push(new InjectMainCssToIndependentCssPlugin()) // 目前只对页面注入了,组件未注入
}
return independentPlugins
}
@@ -0,0 +1,61 @@
const { parse } = require('@babel/parser');
const template = require('@babel/template');
const generator = require('@babel/generator');
const traverse = require('@babel/traverse');
const { generateAsset } = require('./utils');
// 【修改】runtime.js => 全局共享 global.webpackJsonP
class AddShareAbilityToRuntimePlugin {
apply (compiler) {
compiler.hooks.emit.tapPromise('AddShareAbilityToRuntimePlugin', compilation => {
return new Promise((resolve, reject) => {
try {
// debugger
// 修改 runtime.js
const runtimeChunkName = 'common/runtime.js';
const commonRuntimeInfo = compilation.assets[runtimeChunkName];
if (!commonRuntimeInfo) {
resolve();
return;
}
const commonRuntimeStrContent = commonRuntimeInfo.source();
const commonRuntimeAst = parse(commonRuntimeStrContent);
traverse.default(commonRuntimeAst, {
AssignmentExpression (nodePath) {
try {
const leftNode = nodePath.node.left;
if (leftNode.type === 'MemberExpression' && leftNode.object && leftNode.property) {
if (leftNode.object.name === 'global' && leftNode.property.value === 'webpackJsonp') {
const insertCode = 'if(global.webpackJsonp){ return };';
const astNode = template.statements(insertCode)();
const blockNode = nodePath.scope.block;
blockNode.body.body.unshift(...astNode);
nodePath.stop();
}
}
} catch (e) {
console.error('independent.error', 'ShareRuntimeChunkPlugin', e);
}
},
});
const runtimeSource = generator.default(commonRuntimeAst).code;
const runtimeAsset = generateAsset(runtimeSource);
compilation.assets[runtimeChunkName] = runtimeAsset;
Object.values(process.UNI_SUBPACKAGES).forEach(pkgInfo => {
if (pkgInfo.independent) {
compilation.assets[`${pkgInfo.root}/${runtimeChunkName}`] = runtimeAsset;
}
});
resolve();
} catch (e) {
console.error('independent.error', 'AddShareAbilityToRuntimePlugin', e);
reject(e);
}
});
});
}
}
module.exports = AddShareAbilityToRuntimePlugin;
@@ -0,0 +1,76 @@
const fs = require('fs-extra');
const { generateAsset } = require('./utils');
const t = require('@babel/types');
const babelGenerator = require('@babel/generator');
const babelParser = require('@babel/parser');
const { default: babelTraverse } = require('@babel/traverse');
const getWxMpRuntime = require('../runtime/index');
// TODO 这个工作应该放在loader中做,后续优化
class AddWxMpRuntimePlugin {
apply (compiler) {
compiler.hooks.emit.tapPromise('AddWxMpRuntimePlugin', compilation => {
return new Promise((resolve, reject) => {
try {
// 收集独立分包路径下面的所有js文件
// js文件都存储在 compilation.assets中 因为需要注入 require(`${pkgRoot/common/index.js}`)
const thisCompilationAssets = compilation.assets;
const independentPkgsInfo = Object.values(process.UNI_SUBPACKAGES).filter(info => info.independent) || [];
const independentPkgRoots = independentPkgsInfo.map(info => `${info.root}`);
if (!independentPkgRoots.length) {
resolve();
}
const mpRuntimePath = getWxMpRuntime();
const wxMpRuntimeContent = fs.readFileSync(mpRuntimePath, 'utf8');
const ast = babelParser.parse(wxMpRuntimeContent, {
sourceType: 'module',
plugins: ['classProperties'],
});
babelTraverse(ast, {
VariableDeclaration (path) {
const v = path.node.declarations[0];
const name = v.id.name;
const value = v.init;
if (name === 'independentRoots' && t.isArrayExpression(value)) {
value.elements = independentPkgRoots.map(pkgRoot => t.StringLiteral(pkgRoot));
}
},
});
const { code } = babelGenerator.default(ast);
const runtimeAssetsInfo = generateAsset(code);
const mpRuntimeRelativePath = 'common/wxMpRuntime.js';
// 处理app.js
const appJsName = 'app.js';
thisCompilationAssets[mpRuntimeRelativePath] = runtimeAssetsInfo;
const assetInfo = thisCompilationAssets[appJsName];
if (!assetInfo) {
return resolve();
}
const content = assetInfo.source();
thisCompilationAssets[appJsName] = generateAsset(`require('./common/wxMpRuntime.js');${content}`);
// 处理独立分包
independentPkgRoots.forEach(pkgRoot => {
thisCompilationAssets[`${pkgRoot}/${mpRuntimeRelativePath}`] = runtimeAssetsInfo;
const entryJsName = `${pkgRoot}/common/index.js`;
const assetInfo = thisCompilationAssets[entryJsName];
let content = assetInfo.source();
content = generateAsset(`require('./wxMpRuntime.js');${content}`);
thisCompilationAssets[entryJsName] = content;
});
resolve();
} catch (e) {
console.error('AddWxMpRuntimePlugin', e);
reject(e);
}
});
});
}
}
module.exports = AddWxMpRuntimePlugin;
@@ -0,0 +1,65 @@
const t = require('@babel/types');
const babelTraverse = require('@babel/traverse').default;
const babelParser = require('@babel/parser');
const babelGenerator = require('@babel/generator');
const { generateAsset } = require('./utils');
const { collectIndependentJsAssets } = require('./optimize-components-position/util');
const visitor = {
CallExpression (path) {
const funNode = path.node;
// https://developers.weixin.qq.com/miniprogram/dev/reference/api/getApp.html
// FIX: 目前getApp仅支持一个参数(allowDefault),如果后面增加更多的参数以下逻辑需要修改
// 增减判断是否有该参数逻辑
if (t.isIdentifier(path.node.callee)) {
const logicGlobal = '(Function("return this")())'
if (funNode.callee.name === 'getApp' && funNode.arguments.length === 0) {
funNode.callee = t.MemberExpression(t.Identifier(logicGlobal), t.Identifier('getApp'));
} else if (funNode.callee.name === 'App') {
funNode.callee = t.MemberExpression(t.Identifier(logicGlobal), t.Identifier('App'));
}
}
},
};
// 关键:需要在在整个emit阶段的最后(compilation.assets['/pages/chat-im/wxcomponents/...']
class AppInterceptorPlugin {
apply (compiler) {
compiler.hooks.emit.tapPromise('AppInterceptorPlugin', compilation => {
return new Promise(resolve => {
// 收集独立分包路径下面的所有js文件
// js文件都存储在 compilation.assets中 因为需要注入 require(`${pkgRoot/common/index.js}`)
const thisCompilationAssets = compilation.assets;
const independentJsAssets = collectIndependentJsAssets(thisCompilationAssets);
independentJsAssets.forEach(({ jsAssets }) => {
jsAssets.forEach(jsAssetName => {
if (jsAssetName.endsWith('common/wxMpRuntime.js')) {
return;
}
const assetInfo = thisCompilationAssets[jsAssetName];
let assetSource = assetInfo.source();
// 有部分js文件在这里是Buffer类型
if (assetSource instanceof Buffer) {
assetSource = assetSource.toString();
}
const ast = babelParser.parse(assetSource, {
sourceType: 'module',
plugins: ['classProperties'],
});
babelTraverse(ast, visitor);
const { code } = babelGenerator.default(ast);
thisCompilationAssets[jsAssetName] = generateAsset(code);
}
);
});
resolve();
});
});
}
}
module.exports = AppInterceptorPlugin;
@@ -0,0 +1,46 @@
const crypto = require('crypto');
const { generateAsset } = require('./utils');
const emitFileCaches = {};
function md5 (str) {
const hash = crypto.createHash('md5');
hash.update(str);
return hash.digest('hex');
}
function emitFile (filePath, source, compilation) {
const emitFileMD5 = md5(filePath + source);
if (emitFileCaches[filePath] !== emitFileMD5) {
emitFileCaches[filePath] = emitFileMD5;
compilation.assets[filePath] = generateAsset(source);
}
}
// 为独立分包【生成】入口执行文件,代替主包中的app.js
class GenerateIndepndentEntryPlugin {
apply (compiler) {
compiler.hooks.emit.tapPromise('GenerateIndepndentEntryPlugin', compilation => {
return new Promise((resolve, reject) => {
try {
// debugger
const independentPkgs = Object.values(process.UNI_SUBPACKAGES).filter(subPkgItem => subPkgItem.independent) || [];
const independentEntry = independentPkgs.map(independentPkgItem => {
return {
file: `${independentPkgItem.root}/common/index.js`,
source: `require('runtime.js');require('library.js');require('vendor.js');require('main.js');`,
};
});
independentEntry.forEach(({ file, source }) => emitFile(file, source, compilation));
resolve();
} catch (e) {
console.error('independent.error', 'GenerateIndepndentEntryPlugin', e);
reject(e);
}
});
});
}
}
module.exports = GenerateIndepndentEntryPlugin;
@@ -0,0 +1,36 @@
const path = require('path');
const { normalizePath, generateAsset } = require('./utils');
// TODO 换个位置?
// 独立分包的页面或者组件中【注入】 require('index.js') => 代替 app.js 功效
class InjectEntryJsToIndependentPlugin {
apply (compiler) {
compiler.hooks.emit.tapPromise('InjectEntryJsToIndependentPlugin', compilation => {
return new Promise((resolve, reject) => {
try {
// debugger
Object.keys(process.UNI_SUBPACKAGES).forEach(root => {
const pkgInfo = process.UNI_SUBPACKAGES[root];
if (!pkgInfo.independent) return;
const subPackageVendorPath = normalizePath(path.join(root, 'common'));
Object.keys(compilation.assets).forEach(name => {
// 是个js文件都需要加上(不限制组件或者页面
if (path.extname(name) === '.js' && name.startsWith(root + '/') && !name.startsWith(subPackageVendorPath)) {
const originalSource = compilation.assets[name].source();
const entryRuntime = normalizePath(path.relative(path.dirname(name), subPackageVendorPath));
const source = `require('${entryRuntime}/index.js');${originalSource}`;
compilation.assets[name] = generateAsset(source);
}
});
});
resolve();
} catch (e) {
console.error('independent.error', 'InjectEntryJsToIndependentPlugin', e);
reject(e);
}
});
});
}
}
module.exports = InjectEntryJsToIndependentPlugin;
@@ -0,0 +1,138 @@
const fs = require('fs-extra');
const path = require('path');
const { generateAsset } = require('./utils');
const {
getIndependentPkgRoots,
getIndependentEntryPages
} = require('@dcloudio/uni-mp-weixin/lib/independent-plugins/optimize-components-position/util');
const { wxComponentsStr } = require('./optimize-components-position/constant');
const weuiMiniprogramDir = 'weui-mp';
function generateCssSource (pkgMainCssPath, pkgLibraryCssPath, wxssSourceInfo) {
const platforms = ['mp-weixin', 'mp-qq', 'mp-toutiao'];
const presetStyle = platforms.includes(process.env.UNI_PLATFORM) ? '[data-custom-hidden="true"],[bind-data-custom-hidden="true"]{display: none !important;}' : '';
const mainCssImport = pkgMainCssPath ? `@import '/${pkgMainCssPath}';` : '';
const libraryCssImport = pkgLibraryCssPath ? `@import '/${pkgLibraryCssPath}';` : '';
return `${mainCssImport}${libraryCssImport}
${wxssSourceInfo.source()}
${presetStyle}`;
}
function copyWeuiCssToIndependent (independentRoot) {
const weuiCssRelativePath = `wxcomponents/${weuiMiniprogramDir}/weui-wxss/dist/style/weui.wxss`;
const fromPath = path.resolve(process.env.UNI_INPUT_DIR, weuiCssRelativePath);
const toPath = path.resolve(process.env.UNI_OUTPUT_DIR, `${independentRoot}/${weuiCssRelativePath}`);
if (fs.existsSync(fromPath)) {
fs.copySync(fromPath, toPath);
return true;
} else {
console.warn('添加weui组件库到wxcomponents目录下');
}
return false;
}
function tryInsertWeuiCss (independentRoot, originalWxssStr) {
const manifestConfig = process.UNI_MANIFEST;
const weixinConfig = manifestConfig['mp-weixin'] || {};
// 如果使用了weui,则需要注入weui样式
const useExtendedWeUi = !!(weixinConfig.useExtendedLib || {}).weui;
// 复制
const successOrNot = copyWeuiCssToIndependent(independentRoot);
const insertStr = `@import '/${independentRoot}/wxcomponents/${weuiMiniprogramDir}/weui-wxss/dist/style/weui.wxss'`;
return (successOrNot && useExtendedWeUi) ? `${insertStr};${originalWxssStr}` : originalWxssStr;
}
// 独立分包页面不受app.wxss影响
// 独立分包中所有的页面需要导入main.wxss
class InjectMainCssToIndependentCssPlugin {
apply (compiler) {
compiler.hooks.emit.tapPromise('InjectMainCssToIndependentCssPlugin', compilation => {
return new Promise((resolve, reject) => {
try {
const thisCompilationAssets = compilation.assets;
const indendentRoots = getIndependentPkgRoots();
indendentRoots.forEach(indendentRoot => {
let pkgMainCssPath = `${indendentRoot}/common/main.wxss`;
let pkgLibraryCssPath = `${indendentRoot}/common/library.wxss`;
if(!thisCompilationAssets[pkgMainCssPath]){
pkgMainCssPath = '';
}
if(!thisCompilationAssets[pkgLibraryCssPath]){
pkgLibraryCssPath = '';
}
const pkgPagesPath = getIndependentEntryPages(indendentRoot);
// const cacheSet = new Set();
// 获取所有页面和组件
// findAllPagesAndComponentsByIndependentRoot(thisCompilationAssets, indendentRoot, pkgPagesPath, cacheSet);
// const allPagesAndCompoents = [...cacheSet];
// 关键,app.wxss和页面.wxss 对组件的影响是一样的。只需要注入到页面即可
// 记:Component构造页面的化,不需要注入app.wxss。uniapp不存在该情况即页面均是通过Page构造,因此向页面注入
// https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#%E7%BB%84%E4%BB%B6%E6%A0%B7%E5%BC%8F%E9%9A%94%E7%A6%BB
pkgPagesPath.forEach(pageAndComponentPath => {
if (pageAndComponentPath.startsWith('/')) {
pageAndComponentPath = pageAndComponentPath.substring(1);
}
if (pageAndComponentPath.indexOf(wxComponentsStr) >= 0) {
return;
}
const pageWxssPath = `${pageAndComponentPath}.wxss`;
const wxssSourceInfo = thisCompilationAssets[pageWxssPath];
if (!wxssSourceInfo) { // 有可能确实没有产出.wxss文件
console.log('invalid wxssSourceInfo', pageAndComponentPath);
return;
}
let wxssSource = generateCssSource(pkgMainCssPath, pkgLibraryCssPath, wxssSourceInfo);
wxssSource = tryInsertWeuiCss(indendentRoot, wxssSource);
thisCompilationAssets[pageWxssPath] = generateAsset(wxssSource);
});
});
resolve();
} catch (e) {
console.error('independent.error', 'InjectMainCssToIndependentCssPlugin', e);
reject(e);
}
});
});
}
}
module.exports = InjectMainCssToIndependentCssPlugin;
//
// function findAllPagesAndComponentsByIndependentRoot (thisCompilationAssets, independentRoot, pageOrComponents = [], cacheSet = new Set()) {
// pageOrComponents.forEach(pageOrComponentPath => {
// // 防止递归
// const recured = cacheSet.has(pageOrComponentPath);
// if (recured) return;
// cacheSet.add(pageOrComponentPath);
//
// pageOrComponentPath = getNewComponentPathInIndependentPkg(independentRoot, pageOrComponentPath);
// if (pageOrComponentPath.startsWith('/')) {
// pageOrComponentPath = pageOrComponentPath.substring(1);
// }
// const pathWithSuffix = `${pageOrComponentPath}.json`;
// const assetInfo = thisCompilationAssets[pathWithSuffix]; // 原生组件的json文件在copy时保存到了 compilationAssets
// const jsonObj = assetInfo && JSON.parse(assetInfo.source().toString());
//
// if (!jsonObj) {
// console.error('independent.error.recurIndependentJson', pageOrComponentPath);
// return;
// }
//
// const usingComponents = Object.values(jsonObj.usingComponents || {});
// findAllPagesAndComponentsByIndependentRoot(thisCompilationAssets, independentRoot, usingComponents, cacheSet);
// });
// }
@@ -0,0 +1,10 @@
class ModifyUniAppWebpackConfigPlugin {
apply (compiler) {
compiler.hooks.environment.tap('ModifyUniAppWebpackConfigPlugin', () => {
// 不注册webpack内置的splitchunksplugin
delete compiler.options.optimization.splitChunks;
});
}
}
module.exports = ModifyUniAppWebpackConfigPlugin;
@@ -0,0 +1,112 @@
const { wxComponentsStr } = require('./constant');
const fs = require('fs-extra');
const path = require('path');
const { generateAsset } = require('./util');
module.exports = class Analyze {
constructor (emitFileMap, AnalyzeWxcomponentDependency, compilation) {
this.emitFileMap = emitFileMap;
this.compilation = compilation;
this.AnalyzeWxcomponentDependency = AnalyzeWxcomponentDependency;
}
readFileSync (file) {
if (!file.startsWith(process.env.UNI_INPUT_DIR)) {
file = path.resolve(process.env.UNI_INPUT_DIR, file.substring(1, Number.MAX_SAFE_INTEGER));
}
const wxComponentAbsPath = `${process.env.UNI_INPUT_DIR}/${wxComponentsStr}`;
if (file.startsWith(wxComponentAbsPath)) {
return fs.readFileSync(file);
} else {
const assets = this.compilation.assets;
const pathWithoutRelative = path.resolve(file);
const relativePath = path.relative(process.env.UNI_INPUT_DIR, pathWithoutRelative);
const memoryFileInfo = assets[relativePath];
if (memoryFileInfo) {
return memoryFileInfo.source();
}
// 针对json文件,当前由 generate-json 发出,所以资源尚未同步到compilations.assets中
return JSON.stringify(this.emitFileMap.get(relativePath));
}
}
existsSync (file) {
// 类似补丁吧,传过来的路径可能不是/Users
if (!file.startsWith(process.env.UNI_INPUT_DIR)) {
file = path.resolve(process.env.UNI_INPUT_DIR, file.substring(1));
}
const wxComponentAbsPath = `${process.env.UNI_INPUT_DIR}/${wxComponentsStr}`;
if (file.startsWith(wxComponentAbsPath)) {
return fs.existsSync(file);
}
// vue组件的js文件忽略
// if (file.endsWith('.js')) {
// return false;
// }
const assets = this.compilation.assets;
const pathWithoutRelative = path.resolve(file);
const relativePath = path.relative(process.env.UNI_INPUT_DIR, pathWithoutRelative);
// 针对json文件,当前由 generate-json 发出,所以资源尚未同步到compilations.assets中
if (relativePath.endsWith('.json')) {
return !!this.emitFileMap.get(relativePath);
}
return !!assets[relativePath];
}
findAllWxComponentsDependency (componentsPath, useMemoryCache = false) {
const context = process.env.UNI_INPUT_DIR;
let instance;
if (useMemoryCache) {
instance = new this.AnalyzeWxcomponentDependency(context, this.readFileSync.bind(this), this.existsSync.bind(this));
} else {
instance = new this.AnalyzeWxcomponentDependency(context);
}
return {
dependFiles: instance.getDepsByComponents(componentsPath),
allComponents: [...instance.allComponents],
};
}
copyWxComponent (pkgRoot, originalFilePath, targetPath) {
const thisCompilationAssets = this.compilation.assets;
const suffix = path.extname(originalFilePath);
if (!['.js', '.json', '.wxss'].includes(suffix)) {
return fs.copySync(originalFilePath, targetPath);
}
let jsonSource = fs.readFileSync(originalFilePath, 'utf8');
const assetPath = path.relative(process.env.UNI_OUTPUT_DIR, targetPath);
if (suffix === '.js') {
// 计算到 root/common/index 的相对路径
const relativeToDist = path.relative(path.dirname(assetPath), `${pkgRoot}/common/index.js`);
jsonSource = `require('${relativeToDist}');${jsonSource}`;
thisCompilationAssets[assetPath] = generateAsset(jsonSource);
}
// 后续需要更新组件引用路径,所以不采用文件复制方式
// json 后面需要修改包外组件引用路径:copy-outer-components-for-independent
// wxss 需要注入 全局样式:inject-main-css-to-independent-plugin
if (['.json', '.wxss'].includes(suffix)) {
thisCompilationAssets[assetPath] = generateAsset(jsonSource);
}
}
getDependFiles (obj, wxComponentFileDependencyCache, useMemoryCache = false) {
let tmpAllComponents = [];
for (const pkgRoot in obj) {
const wxComponents = [...obj[pkgRoot]];
wxComponents.forEach(wxComponent => {
if (!wxComponentFileDependencyCache[wxComponent]) {
const {
dependFiles,
allComponents
} = this.findAllWxComponentsDependency([wxComponent], useMemoryCache);
tmpAllComponents = [...tmpAllComponents, ...allComponents];
wxComponentFileDependencyCache[wxComponent] = dependFiles || [];
}
});
}
return new Set(tmpAllComponents);
}
};
@@ -0,0 +1,12 @@
module.exports = {
appJsonFileName: 'app.json',
wxComponentsStr: 'wxcomponents',
weuiComponentStr: 'weui-miniprogram',
mainPkgName: 'mainPkg',
outerComponents: 'vueOuterComponents',
componentType: {
wxComponent: 'wxComponent',
vueComponent: 'vueComponent',
unknown: 'unknown'
}
};
@@ -0,0 +1,229 @@
const path = require('path');
const Analyze = require('./analyze');
const { wxComponentsStr, outerComponents, weuiComponentStr } = require('./constant');
const { generateAsset } = require('./util');
const {
collectAllOutSideComponentsMap,
getIndependentPkgRoots,
getIndependentEntryPages,
getGlobalComponentKeyByGlobalComponentPath,
copyAllWxComponentsFiles,
collectPkgCopyFiles,
getNewComponentPathInIndependentPkg,
getJsonByPageOrComponentPath
} = require('./util');
// 原则:原生组件不允许使用全局组件
// 忽略原生组件(wxComponents)使用全局组件的情况
function recurIndependentJson (independentRoot, independentPages, sourceRepo, handler, cacheSet = new Set()) {
independentPages.forEach(independentPage => {
// 避免无限递归
const recured = cacheSet.has(independentPage);
if (recured) return;
cacheSet.add(independentPage);
// 关键:映射到独立分包下面的组件路径
const newComponentPath = getNewComponentPathInIndependentPkg(independentRoot, independentPage);
const {
content: jsonObj, fromAssetsFlag
} = getJsonByPageOrComponentPath(newComponentPath, sourceRepo);
if (!jsonObj) {
// console.log('independent.recurIndependentJson', newComponentPath);
return;
}
// 处理 newComponentPath.json 中的包外组件路径
const usingComponents = jsonObj.usingComponents || {};
for (let componentKey in usingComponents) {
const componentPath = usingComponents[componentKey];
if (componentPath.indexOf(weuiComponentStr) >= 0) {
continue;
}
handler(usingComponents, componentKey);
recurIndependentJson(independentRoot, [componentPath], sourceRepo, handler, cacheSet);
}
if (fromAssetsFlag) {
sourceRepo.compilationAssets[`${newComponentPath}.json`] = generateAsset(JSON.stringify(jsonObj));
}
});
}
// TODO watch 只针对发生变化的文件
class Index extends Analyze {
init () {
const emitFileMap = this.emitFileMap;
const independentRoots = getIndependentPkgRoots();
const outSideComponentsMap = {}; // 引用的包外组件(vue组件和原生组件)
independentRoots.forEach(independentRoot => {
const independentPages = getIndependentEntryPages(independentRoot);
let cacheSet = new Set();
let cacheGlobalUsageMap = new Map();
// 收集包外组件
const collectOuterCompos = independentPage => collectAllOutSideComponentsMap(independentRoot, emitFileMap, independentPage, cacheSet, cacheGlobalUsageMap);
independentPages.forEach(collectOuterCompos);
// 如果是原生组件,则忽略wxComponents以外的组件
cacheSet = [...cacheSet].filter(componentPath => {
if (componentPath.startsWith('/')) {
componentPath = componentPath.substring(1);
}
const isVueComponent = emitFileMap.get(`${componentPath}.json`);
const isWxComponent = componentPath.startsWith(`${wxComponentsStr}`);
// TODO weui组件
return !!(isVueComponent || isWxComponent);
});
// 暂时只收集包外的vue组件和原生组件(wxComponents)
outSideComponentsMap[independentRoot] = {
outerComponentSet: new Set(cacheSet),
globalComponentsMap: cacheGlobalUsageMap
};
});
// 独立分包使用到[全局组件]和[入口页面]作为[文件依赖分析]的入口
const componentFileCache = {};
for (let independentRoot in outSideComponentsMap) {
const info = outSideComponentsMap[independentRoot];
this.copyAndUpdateJson(independentRoot, info, componentFileCache);
}
}
copyAndUpdateJson (independentRoot, info, componentFileCache) {
const { outerComponentSet, globalComponentsMap } = info;
this.getDependFiles({ [independentRoot]: outerComponentSet }, componentFileCache, true);
// 1. 先复制
this.copyOuterComponents(independentRoot, outerComponentSet, componentFileCache);
// 2. 更新组件json中包外组件引用路径
this.updateIndependentJson(independentRoot, globalComponentsMap);
}
updateIndependentJson (independentRoot, globalComponentsMap) {
// 1. 先添加全局组件依赖
this.addGlobalComponentReference(independentRoot, globalComponentsMap);
// 2. 更新显示引用包外组件路径
this.updateOuterComponentReference(independentRoot);
}
// pages/chat-im/vueOuterComponents/components/navigation-bar.json mini-icon引用出错?
// 先处理全局组件:将全局组件引用添加到json文件中
// 整体思路:
// 1. 在init函数中,收集了独立分包用到的所有全局组件(包括包外组件用到的全局组件),
// 2. 保存全局组件被那些页面或者组件使用
// 3. 复制包外组件(全局组件只是包外组件的一部分)
// 4. 由于独立分包不能使用全局组件,所以该方法将全局组件路径添加到独立分包下的组件或页面关联的json文件中,确保可以访问到。
addGlobalComponentReference (independentRoot, globalComponentsMap) {
const globalComponentInfoMap = getGlobalComponentKeyByGlobalComponentPath();
for (let [globalComponentPath, componentSetWhoUsedGlobalCompo] of globalComponentsMap) {
// weui 暂时先不处理
if (globalComponentPath.indexOf(weuiComponentStr) >= 0) {
continue;
}
if (globalComponentPath.indexOf(weuiComponentStr) >= 0) {
continue;
}
const componentKey = globalComponentInfoMap[globalComponentPath];
if (globalComponentPath.startsWith('/')) {
globalComponentPath = globalComponentPath.substring(1);
}
const globalComponentReplacePath = getNewComponentPathInIndependentPkg(independentRoot, globalComponentPath);
if (globalComponentReplacePath === globalComponentPath) return; // 理论上不会走
const compilationAssets = this.compilation.assets;
// pages均为vue文件
[...componentSetWhoUsedGlobalCompo].forEach(componentWhoUsedGlobalCompo => {
// 获取在 independentRoot 目录下的新路径(独立分包内引用的包外组件也有可能用到全局组件,获取该包外组件在独立分包内的新路径)
componentWhoUsedGlobalCompo = getNewComponentPathInIndependentPkg(independentRoot, componentWhoUsedGlobalCompo);
// 获取该组件json文件内容
// 分包内的vue组件对应json存储在emitFileMap中
// 分包外vue组件由于前面的复制,内容保存在assets中
const {
content: pageObj,
fromAssetsFlag // json内容是否来自assets(还可能来自emiFileMap
} = getJsonByPageOrComponentPath(componentWhoUsedGlobalCompo, {
emitFileMap: this.emitFileMap, compilationAssets
});
const usingComponents = pageObj.usingComponents || {};
// 如果没有同名标签,则使用全局组件(优先使用显示声明的标签-针对同名标签)
if (!usingComponents[componentKey]) {
usingComponents[componentKey] = `/${globalComponentReplacePath}`;
}
// 如果json内容来自emiFileMap(可能还没同步到assets上
// emitFileMap 后面会统一挂到assets上
if (!fromAssetsFlag) return;
delete pageObj.usingGlobalComponents
compilationAssets[`${componentWhoUsedGlobalCompo}.json`] = generateAsset(JSON.stringify(pageObj));
});
}
}
updateOuterComponentReference (independentRoot) {
const sourceRepo = {
emitFileMap: this.emitFileMap,
compilationAssets: this.compilation.assets
};
const independentPages = getIndependentEntryPages(independentRoot);
recurIndependentJson(independentRoot, independentPages, sourceRepo, (usingComponents, componentKey) => {
const componentPath = usingComponents[componentKey];
const newComponentPath = getNewComponentPathInIndependentPkg(independentRoot, componentPath);
if (newComponentPath && newComponentPath !== componentPath) {
usingComponents[componentKey] = `/${newComponentPath}`;
}
});
}
copyOuterComponents (independentRoot, outerComponentSet, componentFileCache) {
let copyFiles = collectPkgCopyFiles(outerComponentSet, componentFileCache);
const thisCompilationAssets = this.compilation.assets;
// TODO 组件依赖分许的时候需要记录 绝对路径(js/css/wxml) 进行模块引用的文件,输出后需要更改为相对路径,
copyAllWxComponentsFiles(independentRoot, copyFiles, (originalFilePath, targetPath, relativePath) => {
// 原生组件
if (relativePath.indexOf(wxComponentsStr) >= 0) {
return this.copyWxComponent(independentRoot, originalFilePath, targetPath);
}
// vue组件
const assetInfo = thisCompilationAssets[relativePath];
let assetSource = assetInfo && assetInfo.source();
// json文件此时还没有同步到 assets 上
if (!assetSource && relativePath.endsWith('.json')) {
assetSource = JSON.stringify(this.emitFileMap.get(relativePath));
}
if (!assetSource) {
console.error('independent.error', 'invalid assetSource');
}
const targetPrefix = `${independentRoot}/${outerComponents}`;
const targetJsAssetName = `${targetPrefix}/${relativePath}`;
if (relativePath.endsWith('.js')) {
const originalAsset = thisCompilationAssets[relativePath];
const originalSource = originalAsset && originalAsset.source;
// 见 generate-component
const __$wrappered = originalSource && originalSource.__$wrappered;
if (__$wrappered) {
return;
}
const relativeToDist = path.relative(path.dirname(targetJsAssetName), `${independentRoot}/common/index.js`);
assetSource = `require('${relativeToDist}');${assetSource}`;
}
thisCompilationAssets[targetJsAssetName] = generateAsset(assetSource);
});
}
}
module.exports = Index;
@@ -0,0 +1,55 @@
const { mainPkgName } = require('../constant');
function analyzeGoDirection (usageMap, appJson, emitFileMap) {
const copyComponentsForNormalPkgMap = {};
const copyComponentsForMainPkg = new Set();
const globalComponents = appJson.usingComponents || {};
for (const [originalWxComponentPath, usageInfo] of usageMap) {
const [pkgSet, pageOrComponentPaths] = usageInfo;
// 被主包用到 或者 被多个分包用到,则组件放置主包中
if (pkgSet.has(mainPkgName) || pkgSet.size > 1) {
copyComponentsForMainPkg.add(originalWxComponentPath);
continue;
}
// 到这里说明仅仅被一个普通分包使用,则将该组件复制到该普通分包中去
const pkgRoot = [...pkgSet][0];
const newComponentPath = `/${pkgRoot}${originalWxComponentPath}`;
if (!copyComponentsForNormalPkgMap[pkgRoot]) {
copyComponentsForNormalPkgMap[pkgRoot] = new Set();
}
copyComponentsForNormalPkgMap[pkgRoot].add(originalWxComponentPath);
// 当前组件是否是全局组件
const componentTagInGlobal = Object.keys(globalComponents).find(compoName => originalWxComponentPath === globalComponents[compoName]);
// 该组件 originalWxComponentPath 可能是以全局方式引入有可能是在json文件中声明引用
// 甚至可能是两种方式都存在,只是 tag 不一样
(pageOrComponentPaths || []).forEach(jsonFilePath => {
const jsonFileInfo = emitFileMap.get(jsonFilePath);
// 以全局方式引入该组件
if (componentTagInGlobal) {
delete globalComponents[componentTagInGlobal]; // 从全局组件配置中删除
jsonFileInfo.usingComponents[componentTagInGlobal] = newComponentPath; // 更新当前引用路径为分包路径
}
// 以json文件声明方式引入,则需要更新json文件声明的路径
const usingComponents = jsonFileInfo.usingComponents;
const componentTagInPage = Object.keys(usingComponents).find(compoName => originalWxComponentPath === usingComponents[compoName]);
if (componentTagInPage) {
jsonFileInfo.usingComponents[componentTagInPage] = newComponentPath;
}
if (componentTagInPage || componentTagInGlobal) {
const replaceInfo = `jsonFilePath: ${jsonFilePath}, originalWxComponentPath: ${originalWxComponentPath}, newComponentPath: ${newComponentPath}`;
// console.log(`replace componentPath used only by normal package, ${replaceInfo}`);
}
});
}
return { copyForNormal: copyComponentsForNormalPkgMap, copyForMain: { [mainPkgName]: copyComponentsForMainPkg } };
}
module.exports = analyzeGoDirection;
@@ -0,0 +1,44 @@
// 遍历jsonFiles,根据文件路径前缀判断属于哪个包(vue组件完全基于路径划分包归属
const { appJsonFileName, wxComponentsStr, mainPkgName } = require('../constant');
const { getIndependentPkgRoots, getNormalPkgRoots } = require('../util');
// 原生组件被主包和普通分包的使用情况
function collectWxComponentUsedStatus (emitFileMap) {
const normalSubPkgRoots = getNormalPkgRoots();
const independentSubPkgRoots = getIndependentPkgRoots();
const usageByPkgMap = new Map();
for (const [jsonFileKey, jsonFileInfo] of emitFileMap) {
if (jsonFileKey === appJsonFileName) {
continue;
}
const explicitComponents = jsonFileInfo.usingComponents || {}; // 非全局组件
const usingGlobalWxComponents = jsonFileInfo.usingGlobalComponents || {};
// FIX 全局组件和直接引用的组件名称相同的情况
const currentAllComponents = Object.assign({}, usingGlobalWxComponents, explicitComponents);
// 忽略独立分包下面的组件或页面,即收集主包和普通分包的依赖情况
const findPkg = subPkgRoot => jsonFileKey.startsWith(subPkgRoot);
const independentPkgRoot = independentSubPkgRoots.find(findPkg);
// 忽略独立分包页面
if (independentPkgRoot) continue;
// 找出当前页面所属包(普通分包页面还是主包页面
const pkgName = normalSubPkgRoots.find(findPkg) || mainPkgName;
Object.keys(currentAllComponents).forEach(componentName => {
const componentPath = currentAllComponents[componentName];
if (componentPath.startsWith(`/${wxComponentsStr}`)) {
if (!usageByPkgMap.get(componentPath)) {
usageByPkgMap.set(componentPath, [new Set(), []]);
}
usageByPkgMap.get(componentPath)[0].add(pkgName);
usageByPkgMap.get(componentPath)[1].push(jsonFileKey);
}
});
}
return usageByPkgMap;
}
module.exports = collectWxComponentUsedStatus;
@@ -0,0 +1,55 @@
const collectWxComponentUsedStatus = require('./collect-wx-component-used-status');
const analyzeGoDirection = require('./analyze-go-direction');
const { collectPkgCopyFiles, copyAllWxComponentsFiles } = require('../util');
const { wxComponentsStr, appJsonFileName, mainPkgName } = require('../constant');
const Analyze = require('../analyze');
// 仅仅针对 [主包]和[普通分包]用到[原生组件]进行按需加载
class Index extends Analyze {
init () {
const emitFileMap = this.emitFileMap;
// 1. 获取app.json
const appJson = emitFileMap.get(appJsonFileName);
// 2. 获取每个原生组件(wxComponents)被各分包(主包和普通分包)的引用情况
const usageByPkgMap = collectWxComponentUsedStatus(emitFileMap, appJson);
// 3 处理主包和普通分包中的组件引用情况
const {
copyForNormal,
copyForMain,
} = analyzeGoDirection(usageByPkgMap, appJson, emitFileMap);
// 提示app.json中声明的未被使用的全局原生组件(wxcomponents)
// const rootToWxComponents = Object.assign({}, copyForNormal, copyForMain);
const globalWxComponents = appJson.usingComponents || {};
const wxComponentPaths = [...copyForMain.mainPkg]
// 主包和普通分包用到的原生组件
Object.keys(globalWxComponents).forEach(globalWxComponentKey => {
const globalWxComponentPath = globalWxComponents[globalWxComponentKey];
const isWxComponents = globalWxComponentPath.startsWith(`/${wxComponentsStr}`);
if (isWxComponents && !wxComponentPaths.includes(globalWxComponentPath)) {
delete globalWxComponents[globalWxComponentKey];
// console.log(`global WxComponent(${globalWxComponentKey}) will be removed from global component`);
}
});
// 4. 经过3、4步骤获得每个分包引用的组件情况,对于每个wxcomponent进行依赖分析和提取
const fileCache = {};
this.getDependFiles(copyForNormal, fileCache);
this.getDependFiles(copyForMain, fileCache);
// 5.1 文件复制: 普通分包
Object.keys(copyForNormal).forEach(pkgRot => {
const copyFiles = collectPkgCopyFiles(copyForNormal[pkgRot], fileCache, 'normal pkg');
copyAllWxComponentsFiles(pkgRot, copyFiles);
});
// 5.2 文件复制: 主包
const mainPkgCopyFiles = collectPkgCopyFiles(copyForMain[mainPkgName], fileCache, 'normal pkg');
copyAllWxComponentsFiles('', mainPkgCopyFiles);
}
}
module.exports = Index;
@@ -0,0 +1,29 @@
const CopyOuterComponentsForIndependent = require('./copy-outer-components-for-independent');
const CopyWxComponentOnDemand = require('./copy-wx-components-on-demand');
// @dcloudio/webpack-uni-mp-loader/lib/plugin/index-new.js
// 需要在在上述插件之后执行(获取处理过的json
class DependencyAnalyze {
// wxComponentDependencyAnalyzeHandle 分析微信原生组件的依赖情况
// 后面单独建一个仓库时,指定该类的协议,如需要提供getDepsByComponents方法
constructor () {
this.AnalyzeWxcomponentDependency = require('../../analyze-wxcomponent-dependency/index')
}
init (emitFileMap, compilation) {
const manifestConfig = process.UNI_MANIFEST;
const weixinConfig = manifestConfig['mp-weixin'] || {};
const independentSwitch = !!weixinConfig.independent;
const copyWxComponentsOnDemandSwitch = !!weixinConfig.copyWxComponentsOnDemand; // 默认值false
if (copyWxComponentsOnDemandSwitch) { // 开启按需复制
new CopyWxComponentOnDemand(emitFileMap, this.AnalyzeWxcomponentDependency, compilation).init();
}
if (independentSwitch) {
new CopyOuterComponentsForIndependent(emitFileMap, this.AnalyzeWxcomponentDependency, compilation).init();
}
}
}
module.exports = DependencyAnalyze;
@@ -0,0 +1,157 @@
const path = require('path');
const fs = require('fs-extra');
const { getGlobalUsingComponents } = require('@dcloudio/uni-cli-shared/lib/cache');
const { wxComponentsStr, outerComponents } = require('./constant');
const { generateAsset } = require('../utils');
function getGlobalComponentKeyByGlobalComponentPath () {
const globalUsingComponents = getGlobalUsingComponents();
const globalComponentInfoMap = {};
for (let componentKey in globalUsingComponents) {
const componentPath = globalUsingComponents[componentKey];
globalComponentInfoMap[componentPath] = componentKey;
}
return globalComponentInfoMap;
}
function getIndependentPkgRoots () {
return Object.values(process.UNI_SUBPACKAGES || []).filter(item => item.independent).map(item => item.root);
}
function getNormalPkgRoots () {
return Object.values(process.UNI_SUBPACKAGES || []).filter(item => !item.independent).map(item => item.root);
}
function getIndependentEntryPages (subPkgRoot) {
const subPages = [];
(Object.keys(process.UNI_SUB_PACKAGES_ROOT) || []).forEach(subPkgPagePath => {
const root = process.UNI_SUB_PACKAGES_ROOT[subPkgPagePath];
if (root === subPkgRoot) {
subPages.push(subPkgPagePath);
}
});
return subPages;
}
function getMainPkgPages () {
return (process.UNI_PAGES.pages || []).map(pageInfo => pageInfo.path);
}
function collectPkgCopyFiles (components, wxComponentFileDependencyCache, logPrefix = '') {
const allFiles = [];
components.forEach(component => {
// console.log(logPrefix, `copy component ${component}`);
const cacheFiles = wxComponentFileDependencyCache[component] || [];
allFiles.push(...cacheFiles);
});
return allFiles;
}
function copyAllWxComponentsFiles (key, files = [], copyHandler) {
const targetPathPrefix = `${process.env.UNI_OUTPUT_DIR}/${key}/`;
files.forEach(originalFilePath => {
const relativePath = path.relative(process.env.UNI_INPUT_DIR, originalFilePath);
const targetPath = path.resolve(targetPathPrefix, relativePath);
if (copyHandler) {
return copyHandler(originalFilePath, targetPath, relativePath);
}
fs.copySync(originalFilePath, targetPath);
});
}
// 不带 首杠
function getNewComponentPathInIndependentPkg (independentRoot, componentPath) {
// 相对路径不处理
if (componentPath.startsWith('.')) {
return componentPath;
}
if (componentPath.startsWith('/')) {
componentPath = componentPath.substring(1);
}
if (componentPath.startsWith(`${independentRoot}`)) {
return componentPath;
}
let pathPrefix = `${independentRoot}/`;
if (componentPath.indexOf(wxComponentsStr) >= 0) {
return `${pathPrefix}${componentPath}`;
}
return `${pathPrefix}${outerComponents}/${componentPath}`;
}
// 收集用到的所有包外组件
function collectAllOutSideComponentsMap (independentRoot, emitFileMap, entryPage, cacheForAll = new Set(), cacheForGlobal = new Map()) {
if (entryPage.startsWith('/')) {
entryPage = entryPage.substring(1);
}
const jsonFileInfo = emitFileMap.get(`${entryPage}.json`);
if (!jsonFileInfo) { // 只看vue组件
return;
}
const explicitComponents = jsonFileInfo.usingComponents || {}; // 非全局组件
const usingGlobalComponents = jsonFileInfo.usingGlobalComponents || {}; // 全局组件(忽略原生组件引用全局组件的场景)
const allUsingComponents = Object.assign({}, usingGlobalComponents, explicitComponents);
const allComponentsPath = Object.values(allUsingComponents);
const globalComponents = Object.values(usingGlobalComponents);
allComponentsPath.forEach(componentPath => {
if (!componentPath.startsWith(`/${independentRoot}`)) {
cacheForAll.add(componentPath);
}
// 全局组件
if (globalComponents.includes(componentPath)) {
const originalSet = cacheForGlobal.get(componentPath);
const pageSet = originalSet || new Set();
if (!originalSet) {
cacheForGlobal.set(componentPath, pageSet);
}
pageSet.add(entryPage);
}
collectAllOutSideComponentsMap(independentRoot, emitFileMap, componentPath, cacheForAll, cacheForGlobal);
});
}
function getJsonByPageOrComponentPath (pageOrComponentPath, sourceRepo) {
const { emitFileMap, compilationAssets } = sourceRepo;
if (pageOrComponentPath.startsWith('/')) {
pageOrComponentPath = pageOrComponentPath.substring(1);
}
const pathWithSuffix = `${pageOrComponentPath}.json`;
const assetInfo = compilationAssets[pathWithSuffix]; // 原生组件的json文件在copy时保存到了 compilationAssets
const jsonObj = assetInfo && JSON.parse(assetInfo.source().toString());
try {
return {
content: emitFileMap.get(pathWithSuffix) || jsonObj,
fromAssetsFlag: !!jsonObj
};
} catch (e) {
console.error('util', e);
}
}
function collectIndependentJsAssets (compilationAssets) {
const independentPkgRoots = getIndependentPkgRoots();
const jsAssets = Object.keys(compilationAssets).filter(assetName => assetName.endsWith('.js'));
return independentPkgRoots.map(independentRoot => {
return {
independentRoot, jsAssets: jsAssets.filter(assetName => assetName.startsWith(independentRoot)) || [],
};
});
}
module.exports = {
getJsonByPageOrComponentPath,
getNewComponentPathInIndependentPkg,
collectIndependentJsAssets,
getGlobalComponentKeyByGlobalComponentPath,
getNormalPkgRoots,
getIndependentPkgRoots,
getIndependentEntryPages,
getMainPkgPages,
copyAllWxComponentsFiles,
collectPkgCopyFiles,
collectAllOutSideComponentsMap,
generateAsset
};
@@ -0,0 +1,32 @@
const path = require('path');
const { generateAsset } = require('./utils');
// App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。
class RunDefaultAppPlugin {
apply (compiler) {
compiler.hooks.emit.tapPromise('RunDefaultAppPlugin', compilation => {
return new Promise((resolve, reject) => {
try {
// debugger
const appJsInfo = compilation.assets["app.js"];
if (!appJsInfo) {
console.error('independent.error', 'invalid runDefaultApp');
resolve();
return;
}
const appSource = appJsInfo.source();
// App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。
const runApp = 'if(getApp && !getApp()){ App({}) }';
compilation.assets["app.js"] = generateAsset(`${appSource};${runApp}`);
resolve();
} catch (e) {
console.error('independent.error', 'RunDefaultAppPlugin', e);
reject(e);
}
});
});
}
}
module.exports = RunDefaultAppPlugin;
@@ -0,0 +1,258 @@
const GraphHelpers = require('webpack/lib/GraphHelpers');
const { normalizePath } = require('@dcloudio/uni-cli-shared');
const getSplitChunks = require('@dcloudio/vue-cli-plugin-uni/lib/split-chunks');
const path = require('path');
const mainPath = normalizePath(path.resolve(process.env.UNI_INPUT_DIR, 'main.'));
const mainPkgName = 'mainPkg';
function getAllEntryPointOfChunkGroup (chunkGroup, entryPointSet) {
if (chunkGroup.isInitial()) {
return entryPointSet.add(chunkGroup);
}
const parentChunkGroups = [...chunkGroup.parentsIterable];
parentChunkGroups.forEach(parentChunkGroup => getAllEntryPointOfChunkGroup(parentChunkGroup, entryPointSet));
}
function getChunkToEntryPointsMap (allChunks) {
const chunkToEntryPointsMap = new Map();
allChunks.forEach(chunkItem => {
const chunkGroups = [...chunkItem.groupsIterable];
const tmpEntryPointSet = new Set();
chunkToEntryPointsMap.set(chunkItem, tmpEntryPointSet);
chunkGroups.forEach(chunkGroup => {
getAllEntryPointOfChunkGroup(chunkGroup, tmpEntryPointSet);
});
});
return chunkToEntryPointsMap;
}
function baseTest (module) {
if (module.type === 'css/mini-extract') {
return false;
}
if (module.resource) {
const resource = normalizePath(module.resource);
if (
resource.indexOf('.vue') !== -1 ||
resource.indexOf('.nvue') !== -1 ||
resource.indexOf(mainPath) === 0 // main.js
) {
return false;
}
}
return true;
}
class SplitHandler {
constructor (chunks = [], compilation, cacheGroups, chunkFilter, removeModuleFromChunkFilter = () => true) {
this.chunks = chunks || [];
this.chunkFilter = chunkFilter;
this.cacheGroups = cacheGroups;
this.compilation = compilation;
this.removeModuleFromChunkFilter = removeModuleFromChunkFilter;
this.chunksInfoMap = new Map();
}
addModuleToChunksInfoMap (module, chunks, newChunkName) {
let info = this.chunksInfoMap.get(newChunkName);
if (!info) {
info = {
modules: new Set(),
chunks: new Set(),
};
this.chunksInfoMap.set(newChunkName, info);
}
info.modules.add(module);
chunks.forEach(chunk => info.chunks.add(chunk));
}
checkTest (module, test) {
if (typeof test === 'function') {
if (test(module, module.getChunks())) {
return true;
}
} else if (test instanceof RegExp) {
if (module.nameForCondition && test.test(module.nameForCondition())) {
return true;
}
for (const chunk of module.getChunks()) {
if (chunk.name && test.test(chunk.name)) {
return true;
}
}
}
return false;
}
getHitCacheGroups (module) {
const hitCacheGroups = [];
const cacheGroups = this.cacheGroups;
for (const key of Object.keys(cacheGroups)) {
const cacheInfo = cacheGroups[key];
if (!cacheInfo) {
continue;
}
if (this.checkTest(module, cacheInfo.test)) {
hitCacheGroups.push({ newChunkName: cacheInfo.name, priority: cacheInfo.priority || 0 });
}
}
if (hitCacheGroups.length) {
return hitCacheGroups.sort((b, a) => a.priority - b.priority)[0];
}
return null;
}
start () {
const allModulesSet = new Set();
this.chunks.forEach(chunk => {
chunk.getModules().forEach(module => allModulesSet.add(module));
});
this.splitHandler([...allModulesSet]);
}
filter (module) {
// 获取chunks和this.chunks的交集部分
const filterOne = this.chunks.filter(targetChunk => module.chunksIterable.has(targetChunk)) || [];
// 处理uniapp提供的过滤,见split-chunks文件
return filterOne.filter(this.chunkFilter);
}
splitHandler (allModulesUsedByIndependent) {
// 遍历独立分包中用到的模块,测试其所在的cacheGroup
// 每个模块在这里至多会生成或加入到一个newChunk中
for (const module of allModulesUsedByIndependent) {
const hitGroup = this.getHitCacheGroups(module);
if (!hitGroup) {
continue;
}
this.addModuleToChunksInfoMap(module, this.filter(module), hitGroup.newChunkName);
}
// 遍历 chunksInfoMap
for (const [chunkName, newChunkInfo] of this.chunksInfoMap) {
const newChunk = this.compilation.addChunk(chunkName);
newChunk.chunkReason = 'split chunk for independent';
for (const module of newChunkInfo.modules) {
GraphHelpers.connectChunkAndModule(newChunk, module);
[...newChunkInfo.chunks].forEach(chunk => {
if (this.removeModuleFromChunkFilter(chunk)) {
chunk.removeModule(module);
chunk.split(newChunk);
}
});
}
}
}
}
class SplitIndependentChunksPlugin {
generateCacheGroups () {
const cacheGroups = {};
Object.keys(process.UNI_SUBPACKAGES).forEach(root => {
const pkgInfo = process.UNI_SUBPACKAGES[root];
if (pkgInfo.independent) {
cacheGroups[root] = {
[root + '/commonsVendor']: {
test: /[\\/]node_modules[\\/]/,
minSize: 0,
minChunks: 1,
name: normalizePath(path.join(root, 'common/library')),
priority: 2,
chunks: 'all',
},
[root + '/commons']: {
priority: 1,
name: normalizePath(path.join(root, 'common/vendor')),
test: (module) => {
if (!baseTest(module)) {
return false;
}
return true;
},
},
};
}
});
const splitChunkConfig = getSplitChunks();
cacheGroups[mainPkgName] = splitChunkConfig.cacheGroups;
return { cacheGroups, chunkFilter: splitChunkConfig.chunks };
}
apply (compiler) {
compiler.hooks.thisCompilation.tap('SplitIndependentChunksPlugin', compilation => {
compilation.hooks.optimizeChunksAdvanced.tap('SplitIndependentChunksPlugin', chunks => {
try {
const independentPkgRoot = Object.values(process.UNI_SUBPACKAGES).filter(rootInfo => rootInfo.independent).map(rootInfo => rootInfo.root);
const allPkgRootMap = {};
const mainPkgChunks = [];
for (const chunk of chunks) {
const chunkName = chunk.name;
if (!chunkName) {
continue;
}
const root = independentPkgRoot.find(root => chunkName.startsWith(root));
if (!root) {
mainPkgChunks.push(chunk);
continue;
}
if (!allPkgRootMap[root]) {
allPkgRootMap[root] = [];
}
allPkgRootMap[root].push(chunk);
}
const { cacheGroups, chunkFilter } = this.generateCacheGroups(compiler);
// 找出chunk所有的entryPoint
const chunkToEntryPointsMap = getChunkToEntryPointsMap(compilation.chunks);
const allChunksUsedByIndependentMap = {};
for (const pkgRoot in allPkgRootMap) {
if (!allChunksUsedByIndependentMap[pkgRoot]) {
allChunksUsedByIndependentMap[pkgRoot] = new Set();
}
for (const [chunkItem, entryPointSet] of chunkToEntryPointsMap) {
const filter = entryPoint => entryPoint.name.startsWith(pkgRoot);
const referenceByPkgRoot = [...entryPointSet].find(filter);
// 当前chunk中存在模块被该独立分包下面的页面引用
// uniapp的entry: main.js + page.vue
if (referenceByPkgRoot) {
allChunksUsedByIndependentMap[pkgRoot].add(chunkItem);
}
}
}
// 先分离独立分包
for (const pkgRoot in allPkgRootMap) {
const chunksOfIndependentPkg = allPkgRootMap[pkgRoot];
// 需要将独立分包中用到外部js模块拆分一份到 pkgRoot/vendor.js 中
const allChunksUsedByIndependent = [...allChunksUsedByIndependentMap[pkgRoot]];
// 收集独立分包下面的所有chunks:包内chunks + 包外chunks(主要引用的包外组件)
const outSideChunks = [];
allChunksUsedByIndependent.forEach(chunkItem => {
if (!chunksOfIndependentPkg.includes(chunkItem)) {
outSideChunks.push(chunkItem);
}
});
new SplitHandler([...allChunksUsedByIndependent], compilation, cacheGroups[pkgRoot], chunkFilter, (chunk) => {
if (outSideChunks.includes(chunk)) {
return false;
}
return true;
}).start();
}
// 再分离普通分包和主包
new SplitHandler(mainPkgChunks, compilation, cacheGroups[mainPkgName], chunkFilter).start();
} catch (e) {
console.error('independent.error', 'SplitIndependentChunksPlugin', e);
}
});
});
}
}
module.exports = SplitIndependentChunksPlugin;
+16
View File
@@ -0,0 +1,16 @@
const isWin = /^win/.test(process.platform);
const normalizePath = path => (isWin ? path.replace(/\\/g, '/') : path);
module.exports = {
normalizePath,
generateAsset (stringSource) {
return {
size () {
return Buffer.byteLength(stringSource, 'utf8');
},
source () {
return stringSource;
},
};
},
};
+4
View File
@@ -0,0 +1,4 @@
const path = require('path');
module.exports = function () {
return path.resolve(__dirname) + '/wxMpRuntime.js';
};
+37
View File
@@ -0,0 +1,37 @@
const logicGlobal = Function("return this")();
if (!logicGlobal.wpRuntimeInited) {
logicGlobal.wpRuntimeInited = true;
// https://developers.weixin.qq.com/miniprogram/dev/reference/api/App.html
// 注册小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。
// App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。
const independentRoots = []; // 变量名不能更改,插件通过该名来静态替换值
Object.assign(logicGlobal, {
getApp: function () {
return getApp() || getApp({ allowDefault: true });
},
App: function (appOpts = {}) {
const launchOptions = wx.getLaunchOptionsSync();
const entryPath = launchOptions.path || '';
const isIndependentPage = independentRoots.find(pkgRoot => entryPath.startsWith(pkgRoot));
// 实际上也可以不区分
if (!isIndependentPage) {
return App(appOpts);
}
// TODO 部分App上面挂载的东西 未提供api形式,这里可能不支持
// 目前只针对云医用到的生命周期进行支持
const app = this.getApp();
// const { onLaunch, onShow, onHide, onError, onUnhandledRejection, onThemeChange } = appOpts;
Object.assign(app, appOpts);
app.onLaunch(launchOptions);
wx.onAppShow(opts => app.onShow(opts));
wx.onAppHide(opts => app.onHide(opts));
wx.onError(opts => app.onError(opts));
wx.onUnhandledRejection(opts => app.onUnhandledRejection(opts));
wx.onThemeChange(opts => app.onThemeChange(opts));
},
});
}
File diff suppressed because one or more lines are too long
+143
View File
@@ -0,0 +1,143 @@
const path = require('path')
const t = require('@babel/types')
const uniI18n = require('@dcloudio/uni-cli-i18n')
function generateJsCode (properties = '{}') {
return `
wx.createComponent({
generic:true,
props: ${properties},
render: function(){}
})
`
}
function generateCssCode (filename) {
return `
@import "./${filename}"
`
}
function hasOwn (obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key)
}
module.exports = {
directive: 'wx:',
createScopedSlots (slotName, props, state) {
const componentName = 'scoped-slots-' + slotName
if (!state.componentGenerics) {
state.componentGenerics = Object.create(null)
}
state.componentGenerics[componentName] = true
const attr = props || {}
if (state.options.platform.name === 'mp-weixin') {
attr.class = 'scoped-ref'
}
// 返回多个节点,支持作用域插槽当作普通插槽使用
return [{
type: 'slot',
attr: {
name: slotName
},
children: []
},
{
type: componentName,
attr,
children: []
}
]
},
resolveScopedSlots (slotName, {
genCode,
generate,
ownerName,
parentName,
parentNode,
resourcePath,
paramExprNode,
returnExprNodes,
traverseExpr
}, state) {
if (!state.scopedSlots) {
state.scopedSlots = {}
}
const baseName = `${ownerName}-${parentName}-${slotName}`
let componentName = baseName
if (!hasOwn(state.scopedSlots, baseName)) {
state.scopedSlots[baseName] = 0
}
if (state.scopedSlots[baseName]) {
componentName = baseName + state.scopedSlots[baseName]
}
state.scopedSlots[baseName]++
parentNode.attr['generic:scoped-slots-' + slotName] = componentName
if (state.options.platform.name === 'mp-weixin') {
parentNode.attr['data-vue-generic'] = 'scoped'
}
if (!parentNode.attr.generic) {
parentNode.attr.generic = {}
}
parentNode.attr.generic[slotName] = true
// 生成 scopedSlots 文件,包括 json,js,wxml,wxss,还需要更新 owner 的 usingComponents
if (!state.files) {
state.files = {}
}
const extname = path.extname(resourcePath)
// TODO 需要存储 resourcePath 相关 json
const templateFile = resourcePath.replace(ownerName + extname, componentName + extname)
const templateContent = generate(traverseExpr(returnExprNodes, state), state)
state.files[templateFile] = templateContent
const jsFile = resourcePath.replace(ownerName + extname, componentName + '.js')
const objectProperties = []
if (t.isObjectPattern(paramExprNode)) {
paramExprNode.properties.forEach(property => {
const key = property.key
const value = property.value
const valueObjectProperties = [
t.objectProperty(t.identifier('type'), t.nullLiteral())
]
if (t.isIdentifier(value)) {
if (value.name !== key.name) {
state.errors.add(uniI18n.__('mpWeChat.slotPropNoSupportReanme', { 0: key.name, 1: value.name }))
}
} else if (t.isAssignmentPattern(value)) {
valueObjectProperties.push(t.objectProperty(t.identifier('default'), value.right))
}
objectProperties.push(t.objectProperty(key, t.objectExpression(valueObjectProperties)))
})
} else {
state.errors.add(uniI18n.__('mpWeChat.onlySupportDestructuringSlot', { 0: paramExprNode.name, 1: 'v-slot="{ user }"' }))
}
const jsContent = generateJsCode(genCode(t.objectExpression(objectProperties), true))
state.files[jsFile] = jsContent
try {
// TODO 使用 getPlatformExts 在单元测试报错,改从 state.options.platform 判断
const {
getPlatformExts
} = require('@dcloudio/uni-cli-shared')
const styleExtname = getPlatformExts().style
const styleFile = resourcePath.replace(ownerName + extname, componentName + styleExtname)
const styleContent = generateCssCode(ownerName + styleExtname)
state.files[styleFile] = styleContent
} catch (error) {}
if (!state.generic) {
state.generic = []
}
// 存储,方便后续生成 json
state.generic.push(componentName)
return ''
}
}
+82
View File
@@ -0,0 +1,82 @@
const fs = require('fs')
const path = require('path')
const { parseJson } = require('@dcloudio/uni-cli-shared/lib/json')
const { copyMiniProgramThemeJson } = require('@dcloudio/uni-cli-shared/lib/theme')
const COMPONENTS_DIR_NAME = 'wxcomponents'
module.exports = {
options: {
cssVars: {
'--status-bar-height': '25px',
'--window-top': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-right': '0px'
},
extnames: {
style: '.wxss',
template: '.wxml',
filter: '.wxs'
},
filterTag: 'wxs',
project: 'project.config.json',
subPackages: true,
darkmode: true
},
copyWebpackOptions (platformOptions, vueOptions) {
const CopyWebpackPluginVersion = Number(require('copy-webpack-plugin/package.json').version.split('.')[0])
const copyOptions = [
'sitemap.json',
'ext.json',
'custom-tab-bar',
'functional-pages',
'project.private.config.json'
]
if (process.env.UNI_MP_PLUGIN) {
copyOptions.push({
from: path.resolve(process.env.UNI_INPUT_DIR, 'plugin.json'),
transform: content => JSON.stringify(parseJson(content.toString(), true))
})
}
copyOptions.push(copyMiniProgramThemeJson(platformOptions, vueOptions))
const workers = platformOptions.workers
workers && copyOptions.push(typeof workers === 'object' ? workers.path : workers)
const manifestConfig = process.UNI_MANIFEST
const weixinConfig = manifestConfig['mp-weixin'] || {}
const copyWxComponentsOnDemandSwitch = !!weixinConfig.copyWxComponentsOnDemand // 默认值false
const ignore = ['**/*.vue', '**/*.css'] // v3 会自动转换生成vue,css文件,需要过滤
if (!copyWxComponentsOnDemandSwitch) {
const wxcomponentsDir = path.resolve(process.env.UNI_INPUT_DIR, COMPONENTS_DIR_NAME)
if (fs.existsSync(wxcomponentsDir)) {
copyOptions.push(Object.assign({
from: wxcomponentsDir,
to: COMPONENTS_DIR_NAME
}, CopyWebpackPluginVersion > 5 ? {
globOptions: { ignore }
} : {
ignore
}))
}
}
global.uniModules.forEach(module => {
const wxcomponentsDir = path.resolve(process.env.UNI_INPUT_DIR, 'uni_modules', module, COMPONENTS_DIR_NAME)
if (fs.existsSync(wxcomponentsDir)) {
copyOptions.push(Object.assign({
from: wxcomponentsDir,
to: 'uni_modules/' + module + '/' + COMPONENTS_DIR_NAME
}, CopyWebpackPluginVersion > 5 ? {
globOptions: { ignore }
} : {
ignore
}))
}
})
return copyOptions
}
}
+1
View File
@@ -0,0 +1 @@
[https://dcloud.io/license/uni-app.html](https://dcloud.io/license/uni-app.html)
+21
View File
@@ -0,0 +1,21 @@
{
"name": "@dcloudio/uni-mp-weixin",
"version": "2.0.2-4070620250821001",
"description": "uni-app mp-weixin",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/dcloudio/uni-app.git",
"directory": "packages/uni-mp-weixin"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "fxy060608",
"license": "Apache-2.0",
"uni-app": {
"name": "mp-weixin",
"title": "微信小程序"
},
"gitHead": "83322ec9f7cc592b2052ff78370e12f4689f5966"
}