diff --git a/src/compiler/app-core/bundle-app-core.ts b/src/compiler/app-core/bundle-app-core.ts index e7dc5a38928..00d12ebf9ca 100644 --- a/src/compiler/app-core/bundle-app-core.ts +++ b/src/compiler/app-core/bundle-app-core.ts @@ -54,7 +54,7 @@ export const bundleApp = async (config: d.Config, compilerCtx: d.CompilerCtx, bu }), config.sys.rollup.plugins.json(), imagePlugin(config, buildCtx), - cssTransformer(buildCtx), + cssTransformer(config, compilerCtx, buildCtx), inMemoryFsRead(config, compilerCtx), config.sys.rollup.plugins.replace({ 'process.env.NODE_ENV': config.devMode ? '"development"' : '"production"' diff --git a/src/compiler/browser/create-compiler.ts b/src/compiler/browser/create-compiler.ts index 59913cdfef3..1fe58f01ae2 100644 --- a/src/compiler/browser/create-compiler.ts +++ b/src/compiler/browser/create-compiler.ts @@ -14,14 +14,22 @@ export const createCompiler = () => { diagnostics.length = 0; }; + const getResolvedData = (id: string) => { + return stencilResolved.get(id); + }; + + const setResolvedData = (id: string, r: d.ResolvedStencilData) => { + return stencilResolved.set(id, r); + }; + return { resolveId(importee: string, importer: string) { // import Css from 'stencil?tag=cmp-a&scopeId=sc-cmp-a-md&mode=md!./filepath.css const r = parseStencilImportPath(importee, importer); if (r != null) { - stencilResolved.set(r.resolvedId, r); - return r.resolvedId; + setResolvedData(r.resolvedId, r); + return r; } return null; }, @@ -34,10 +42,11 @@ export const createCompiler = () => { }, async transform(code: string, filePath: string, opts?: d.CompileOptions) { - const r = stencilResolved.get(filePath); + const r = getResolvedData(filePath); if (r != null) { const compileOpts = Object.assign({}, defaultOpts, opts); - compileOpts.file = r.filePath; + compileOpts.type = r.type; + compileOpts.file = r.resolvedFilePath; compileOpts.data = r.data; const results = await compile(code, compileOpts); @@ -55,6 +64,8 @@ export const createCompiler = () => { reset(); }, - reset + reset, + getResolvedData, + setResolvedData, }; }; diff --git a/src/compiler/plugin/plugin.ts b/src/compiler/plugin/plugin.ts index 8624f446821..319ca901538 100644 --- a/src/compiler/plugin/plugin.ts +++ b/src/compiler/plugin/plugin.ts @@ -153,3 +153,59 @@ export async function runPluginTransforms(config: d.Config, compilerCtx: d.Compi return transformResults; } + + +export const runPluginTransformsEsmImports = async (config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx, sourceText: string, id: string) => { + const pluginCtx: PluginCtx = { + config: config, + sys: config.sys, + fs: compilerCtx.fs, + cache: compilerCtx.cache, + diagnostics: [] + }; + + const transformResults: PluginTransformResults = { + code: sourceText, + id: id + }; + + for (const plugin of pluginCtx.config.plugins) { + + if (typeof plugin.transform === 'function') { + try { + let pluginTransformResults: PluginTransformResults | string; + const results = plugin.transform(transformResults.code, transformResults.id, pluginCtx); + + if (results != null) { + if (typeof (results as any).then === 'function') { + pluginTransformResults = await results; + + } else { + pluginTransformResults = results as PluginTransformResults; + } + + if (pluginTransformResults != null) { + if (typeof pluginTransformResults === 'string') { + transformResults.code = pluginTransformResults as string; + + } else { + if (typeof pluginTransformResults.code === 'string') { + transformResults.code = pluginTransformResults.code; + } + if (typeof pluginTransformResults.id === 'string') { + transformResults.id = pluginTransformResults.id; + } + } + } + } + + } catch (e) { + catchError(buildCtx.diagnostics, e); + } + } + } + + buildCtx.diagnostics.push(...pluginCtx.diagnostics); + + return transformResults; +}; diff --git a/src/compiler/rollup-plugins/css-transformer.ts b/src/compiler/rollup-plugins/css-transformer.ts index cde177dce88..f8143ee39b1 100644 --- a/src/compiler/rollup-plugins/css-transformer.ts +++ b/src/compiler/rollup-plugins/css-transformer.ts @@ -1,24 +1,44 @@ import * as d from '../../declarations'; import { createCompiler } from '../browser/create-compiler'; import { Plugin } from 'rollup'; +import { runPluginTransformsEsmImports } from '../plugin/plugin'; -export const cssTransformer = (buildCtx: d.BuildCtx): Plugin => { +export const cssTransformer = (config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx): Plugin => { const compiler = createCompiler(); return { name: 'cssTransformer', + resolveId(importee: string, importer: string) { - return compiler.resolveId(importee, importer); + const r = compiler.resolveId(importee, importer); + if (r != null && r.type === 'css') { + if (KNOWN_PREPROCESSOR_EXTS.has(r.importerExt) && r.importerExt !== r.resolvedFileExt) { + // basically for sass paths without an extension + r.resolvedFileExt = r.importerExt; + r.resolvedFileName += '.' + r.resolvedFileExt; + r.resolvedFilePath += '.' + r.resolvedFileExt; + r.resolvedId = `${r.resolvedFilePath}?${r.params}`; + compiler.setResolvedData(r.resolvedId, r); + } + return r.resolvedId; + } + return null; }, async transform(code: string, id: string) { - const results = await compiler.transform(code, id); - if (results != null) { - buildCtx.diagnostics.push(...results.diagnostics); - return results; + const r = compiler.getResolvedData(id); + if (r != null) { + const pluginTransforms = await runPluginTransformsEsmImports(config, compilerCtx, buildCtx, code, id); + const results = await compiler.transform(pluginTransforms.code, id); + if (results != null) { + buildCtx.diagnostics.push(...results.diagnostics); + return results; + } } return null; } }; }; + +const KNOWN_PREPROCESSOR_EXTS = new Set(['sass', 'scss', 'styl', 'less', 'pcss']); diff --git a/src/compiler/rollup-plugins/stencil-public-plugin.ts b/src/compiler/rollup-plugins/stencil-public-plugin.ts index cd9cc56b532..55b4c41b72a 100644 --- a/src/compiler/rollup-plugins/stencil-public-plugin.ts +++ b/src/compiler/rollup-plugins/stencil-public-plugin.ts @@ -10,7 +10,11 @@ export const stencilRollupPlugin = (): Plugin => { name: 'stencilPlugin', resolveId(importee: string, importer: string) { - return compiler.resolveId(importee, importer); + const r = compiler.resolveId(importee, importer); + if (r != null) { + return r.resolvedId; + } + return null; }, async transform(code: string, id: string, opts: d.CompileOptions = {}) { diff --git a/src/compiler/transformers/stencil-import-path.ts b/src/compiler/transformers/stencil-import-path.ts index af6cdacb80f..24772e00036 100644 --- a/src/compiler/transformers/stencil-import-path.ts +++ b/src/compiler/transformers/stencil-import-path.ts @@ -1,6 +1,5 @@ import * as d from '../../declarations'; -import { DEFAULT_STYLE_MODE, normalizePath } from '@utils'; -import { getScopeId } from '../style/scope-css'; +import { DEFAULT_STYLE_MODE, getFileExt, normalizePath } from '@utils'; import path from 'path'; @@ -13,7 +12,6 @@ export const createStencilImportPath = (type: d.StencilDataType, tagName: string const serializeStencilImportPath = (type: d.StencilDataType, tagName: string, encapsulation: string, modeName: string) => { const data: d.StencilComponentData = { tag: tagName, - scopeId: getScopeId(tagName, modeName) }; if (modeName && modeName !== DEFAULT_STYLE_MODE) { data.mode = modeName; @@ -38,34 +36,39 @@ export const parseStencilImportPath = (importee: string, importer: string) => { const dataParts = importData.split('?'); if (dataParts.length === 2) { - const paramsStr = dataParts[1]; - const params = new URLSearchParams(paramsStr); - const type = params.get('type') as any; + const params = dataParts[1]; + const urlParams = new URLSearchParams(params); + const type = urlParams.get('type') as any; const data: d.StencilComponentData = { - tag: params.get('tag'), - scopeId: params.get('scopeId'), - encapsulation: params.get('encapsulation') || 'none', - mode: params.get('mode') || DEFAULT_STYLE_MODE, + tag: urlParams.get('tag'), + encapsulation: urlParams.get('encapsulation') || 'none', + mode: urlParams.get('mode') || DEFAULT_STYLE_MODE, }; importer = normalizePath(importer); const importerDir = path.dirname(importer); - const filePath = normalizePath(path.resolve(importerDir, importPath)); - const fileName = path.basename(filePath); + const importerExt = getFileExt(importer.split('?')[0]); - let resolvedId = filePath; + const resolvedFilePath = normalizePath(path.resolve(importerDir, importPath)); + const resolvedFileName = path.basename(resolvedFilePath); + const resolvedFileExt = getFileExt(resolvedFileName); + + let resolvedId = resolvedFilePath; if (data.encapsulation === 'scoped' && data.mode && data.mode !== DEFAULT_STYLE_MODE) { - resolvedId += `?${paramsStr}`; + resolvedId += `?${params}`; } const r: d.ResolvedStencilData = { type, resolvedId, - filePath, - fileName, + resolvedFilePath, + resolvedFileName, + resolvedFileExt, + params, data, importee, importer, + importerExt, }; return r; diff --git a/src/declarations/browser-compile.ts b/src/declarations/browser-compile.ts index a47434ddd35..e052bdc75b1 100644 --- a/src/declarations/browser-compile.ts +++ b/src/declarations/browser-compile.ts @@ -36,16 +36,18 @@ export type StencilDataType = 'css' | 'js' | 'ts' | 'tsx' | 'jsx' | 'dts'; export interface ResolvedStencilData { type: StencilDataType; resolvedId: string; - filePath: string; - fileName: string; + resolvedFilePath: string; + resolvedFileName: string; + resolvedFileExt: string; + params: string; data: StencilComponentData; importee: string; importer: string; + importerExt: string; } export interface StencilComponentData { tag: string; - scopeId: string; encapsulation?: string; mode?: string; } diff --git a/src/utils/util.ts b/src/utils/util.ts index 4198c769a48..ae9285ba340 100644 --- a/src/utils/util.ts +++ b/src/utils/util.ts @@ -3,6 +3,16 @@ import { BANNER } from './constants'; import { buildError } from './message-utils'; +export const getFileExt = (fileName: string) => { + if (typeof fileName === 'string') { + const parts = fileName.split('.'); + if (parts.length > 1) { + return parts[parts.length - 1].toLowerCase(); + } + } + return null; +}; + /** * Test if a file is a typescript source file, such as .ts or .tsx. * However, d.ts files and spec.ts files return false.