提交 df4265d9 编写于 作者: fxy060608's avatar fxy060608

feat(app): css scoped

上级 fd63c97c
......@@ -7,7 +7,7 @@ const build_1 = require("./build");
const configResolved_1 = require("./configResolved");
exports.UniAppPlugin = {
name: 'vite:uni-app',
uni: uni_1.uniOptions,
uni: uni_1.uniOptions(),
config() {
return {
build: build_1.buildOptions(),
......
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.uniOptions = void 0;
const path_1 = __importDefault(require("path"));
const uni_shared_1 = require("@dcloudio/uni-shared");
exports.uniOptions = {
copyOptions: { assets: ['hybrid/html'] },
compilerOptions: {
isNativeTag: uni_shared_1.isServiceNativeTag,
isCustomElement: uni_shared_1.isServiceCustomElement,
},
transformEvent: {
tap: 'click',
},
};
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
function uniOptions() {
return {
copyOptions: {
assets: ['hybrid/html'],
targets: [
{
src: uni_cli_shared_1.normalizePath(path_1.default.resolve(process.env.UNI_INPUT_DIR, 'androidPrivacy.json')),
dest: process.env.UNI_OUTPUT_DIR,
},
],
},
compilerOptions: {
isNativeTag: uni_shared_1.isServiceNativeTag,
isCustomElement: uni_shared_1.isServiceCustomElement,
},
transformEvent: {
tap: 'click',
},
};
}
exports.uniOptions = uniOptions;
......@@ -6,7 +6,7 @@ import { configResolved } from './configResolved'
export const UniAppPlugin: UniVitePlugin = {
name: 'vite:uni-app',
uni: uniOptions,
uni: uniOptions(),
config() {
return {
build: buildOptions(),
......
import path from 'path'
import {
isServiceNativeTag,
isServiceCustomElement,
} from '@dcloudio/uni-shared'
import { UniVitePlugin } from '@dcloudio/uni-cli-shared'
import { normalizePath, UniVitePlugin } from '@dcloudio/uni-cli-shared'
export const uniOptions: UniVitePlugin['uni'] = {
copyOptions: { assets: ['hybrid/html'] },
compilerOptions: {
isNativeTag: isServiceNativeTag,
isCustomElement: isServiceCustomElement,
},
transformEvent: {
tap: 'click',
},
export function uniOptions(): UniVitePlugin['uni'] {
return {
copyOptions: {
assets: ['hybrid/html'],
targets: [
{
src: normalizePath(
path.resolve(process.env.UNI_INPUT_DIR, 'androidPrivacy.json')
),
dest: process.env.UNI_OUTPUT_DIR,
},
],
},
compilerOptions: {
isNativeTag: isServiceNativeTag,
isCustomElement: isServiceCustomElement,
},
transformEvent: {
tap: 'click',
},
}
}
import postcss, { ProcessOptions } from 'postcss'
import scopedPlugin from '../src/postcss/plugins/stylePluginScoped'
const styleCode = `{color:red;}`
const deepSelectors = [
['>>> a', ' a', ':deep(a)'],
['a >>> b', 'a b', 'a :deep(b)'],
['a >>> b>c', 'a b>c', 'a :deep(b>c)'],
['>>> a b', ' a b', ':deep(a b)'],
['/deep/ a', ' a', ':deep(a)'],
['a /deep/ b', 'a b', 'a :deep(b)'],
['a /deep/ b>c', 'a b>c', 'a :deep(b>c)'],
['/deep/ a b', ' a b', ':deep(a b)'],
['::v-deep a', ' a', ':deep(a)'],
['a ::v-deep b', 'a b', 'a :deep(b)'],
['a ::v-deep b>c', 'a b>c', 'a :deep(b>c)'],
['::v-deep a b', ' a b', ':deep(a b)'],
['::v-deep a b,::v-deep a', ' a b, a', ':deep(a b),:deep(a)'],
]
const removeDeepCssCodes = Object.create(null)
const rewriteDeepCssCodes = Object.create(null)
deepSelectors.forEach(([selector, removeDeepSelector, rewriteDeepSelector]) => {
removeDeepCssCodes[selector + styleCode] = removeDeepSelector + styleCode
rewriteDeepCssCodes[selector + styleCode] = rewriteDeepSelector + styleCode
})
const processor = postcss([scopedPlugin])
const processorWithVueSfcScoped = postcss([
scopedPlugin,
{
postcssPlugin: 'vue-sfc-scoped',
},
])
const options: ProcessOptions = { from: 'a.css', map: false }
describe('cssScoped', () => {
Object.keys(removeDeepCssCodes).forEach((cssCode) => {
test('remove ' + cssCode, async () => {
return processor.process(cssCode, options).then((result) => {
expect(result.css).toBe(removeDeepCssCodes[cssCode])
})
})
})
Object.keys(rewriteDeepCssCodes).forEach((cssCode) => {
test('rewrite ' + cssCode, async () => {
return processorWithVueSfcScoped
.process(cssCode, options)
.then((result) => {
expect(result.css).toBe(rewriteDeepCssCodes[cssCode])
})
})
})
})
......@@ -10,4 +10,6 @@ export * from './utils'
export * from './easycom'
export * from './constants'
export * from './preprocess'
export * from './postcss'
export { checkUpdate } from './checkUpdate'
import uniPostcssScopedPlugin from './plugins/stylePluginScoped'
export { uniPostcssScopedPlugin }
import { Plugin, PluginCreator, Rule, AtRule } from 'postcss'
import selectorParser from 'postcss-selector-parser'
const scopedPlugin: PluginCreator<string> = () => {
return {
postcssPlugin: 'uni-sfc-scoped',
prepare({ processor: { plugins } }) {
const hasVueSfcScoped = !!plugins.find(
(plugin) => (plugin as Plugin).postcssPlugin === 'vue-sfc-scoped'
)
return {
Rule(rule) {
processRule(rule, hasVueSfcScoped)
},
}
},
}
}
const processedRules = new WeakSet<Rule>()
function processRule(rule: Rule, hasVueSfcScoped: boolean) {
if (
processedRules.has(rule) ||
(rule.parent &&
rule.parent.type === 'atrule' &&
/-?keyframes$/.test((rule.parent as AtRule).name))
) {
return
}
processedRules.add(rule)
rule.selector = selectorParser((selectorRoot) => {
selectorRoot.each((selector) => {
hasVueSfcScoped
? rewriteDeprecatedSelector(selector)
: rewriteSelector(selector, selectorRoot)
})
}).processSync(rule.selector)
}
/**
* @param selector
* @returns
*/
function rewriteDeprecatedSelector(selector: selectorParser.Selector) {
const nodes: selectorParser.Node[] = []
let deepNode: selectorParser.Pseudo | selectorParser.Combinator | undefined
selector.each((n) => {
if (deepNode) {
nodes.push(n)
selector.removeChild(n)
} else {
const { type, value } = n
if (type === 'pseudo' && value === '::v-deep') {
deepNode = n as selectorParser.Pseudo
} else if (
type === 'combinator' &&
(value === '>>>' || value === '/deep/')
) {
deepNode = n as selectorParser.Combinator
}
}
})
if (!deepNode) {
return
}
if (deepNode.type === 'combinator') {
const index = selector.index(deepNode)
if (index > 0) {
selector.insertBefore(deepNode, selectorParser.combinator({ value: ' ' }))
}
}
// remove first combinator
// ::v-deep a{color:red;} => :deep(a){color:red;}
const firstNode = nodes[0]
if (firstNode && firstNode.type === 'combinator' && firstNode.value === ' ') {
nodes.shift()
}
selector.insertBefore(
deepNode,
selectorParser.pseudo({
value: ':deep',
nodes: [selectorParser.selector({ value: '', nodes })],
})
)
selector.removeChild(deepNode)
}
function rewriteSelector(
selector: selectorParser.Selector,
selectorRoot: selectorParser.Root
) {
let node: selectorParser.Node | null = null
// find the last child node to insert attribute selector
selector.each((n) => {
// DEPRECATED ">>>" and "/deep/" combinator
if (
n.type === 'combinator' &&
(n.value === '>>>' || n.value === '/deep/')
) {
n.value = ' '
n.spaces.before = n.spaces.after = ''
// warn(
// `the >>> and /deep/ combinators have been deprecated. ` +
// `Use :deep() instead.`
// )
return false
}
if (n.type === 'pseudo') {
const { value } = n
// deep: inject [id] attribute at the node before the ::v-deep
// combinator.
if (value === ':deep' || value === '::v-deep') {
if (n.nodes.length) {
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
// replace the current node with ::v-deep's inner selector
let last: selectorParser.Selector['nodes'][0] = n
n.nodes[0].each((ss) => {
selector.insertAfter(last, ss)
last = ss
})
// insert a space combinator before if it doesn't already have one
const prev = selector.at(selector.index(n) - 1)
if (!prev || !isSpaceCombinator(prev)) {
selector.insertAfter(
n,
selectorParser.combinator({
value: ' ',
})
)
}
selector.removeChild(n)
} else {
// DEPRECATED usage
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
// warn(
// `::v-deep usage as a combinator has ` +
// `been deprecated. Use :deep(<inner-selector>) instead.`
// )
const prev = selector.at(selector.index(n) - 1)
if (prev && isSpaceCombinator(prev)) {
selector.removeChild(prev)
}
selector.removeChild(n)
}
return false
}
// slot: use selector inside `::v-slotted` and inject [id + '-s']
// instead.
// ::v-slotted(.foo) -> .foo[xxxxxxx-s]
if (value === ':slotted' || value === '::v-slotted') {
rewriteSelector(n.nodes[0], selectorRoot)
let last: selectorParser.Selector['nodes'][0] = n
n.nodes[0].each((ss) => {
selector.insertAfter(last, ss)
last = ss
})
// selector.insertAfter(n, n.nodes[0])
selector.removeChild(n)
// since slotted attribute already scopes the selector there's no
// need for the non-slot attribute.
return false
}
// global: replace with inner selector and do not inject [id].
// ::v-global(.foo) -> .foo
if (value === ':global' || value === '::v-global') {
selectorRoot.insertAfter(selector, n.nodes[0])
selectorRoot.removeChild(selector)
return false
}
}
if (n.type !== 'pseudo' && n.type !== 'combinator') {
node = n
}
})
if (node) {
;(node as selectorParser.Node).spaces.after = ''
} else {
// For deep selectors & standalone pseudo selectors,
// the attribute selectors are prepended rather than appended.
// So all leading spaces must be eliminated to avoid problems.
selector.first.spaces.before = ''
}
}
function isSpaceCombinator(node: selectorParser.Node) {
return node.type === 'combinator' && /^\s+$/.test(node.value)
}
scopedPlugin.postcss = true
export default scopedPlugin
......@@ -3,8 +3,8 @@ import { SFCTemplateCompileOptions } from '@vue/compiler-sfc'
import { isCustomElement } from '@dcloudio/uni-shared'
import {
EXTNAME_VUE_RE,
// parseCompatConfigOnce,
UniVitePlugin,
uniPostcssScopedPlugin,
} from '@dcloudio/uni-cli-shared'
import { VitePluginUniResolvedOptions } from '..'
......@@ -49,6 +49,13 @@ export function initPluginVueOptions(
}
vueOptions.include.push(EXTNAME_VUE_RE)
const styleOptions = vueOptions.style || (vueOptions.style = {})
if (!styleOptions.postcssPlugins) {
styleOptions.postcssPlugins = []
}
// 解析 scoped 中 deep 等特殊语法
styleOptions.postcssPlugins.push(uniPostcssScopedPlugin())
const templateOptions = vueOptions.template || (vueOptions.template = {})
templateOptions.transformAssetUrls = createUniVueTransformAssetUrls(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册