mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-19 13:55:21 +00:00
Merge pull request #471 from sveltejs/missing-css
include css depended upon by entry point, even if also depended on by a lazily-loaded component
This commit is contained in:
@@ -3,7 +3,7 @@ import * as path from 'path';
|
|||||||
import hash from 'string-hash';
|
import hash from 'string-hash';
|
||||||
import * as codec from 'sourcemap-codec';
|
import * as codec from 'sourcemap-codec';
|
||||||
import { PageComponent, Dirs } from '../../interfaces';
|
import { PageComponent, Dirs } from '../../interfaces';
|
||||||
import { CompileResult } from './interfaces';
|
import { CompileResult, Chunk } from './interfaces';
|
||||||
import { posixify } from '../../utils'
|
import { posixify } from '../../utils'
|
||||||
|
|
||||||
const inline_sourcemap_header = 'data:application/json;charset=utf-8;base64,';
|
const inline_sourcemap_header = 'data:application/json;charset=utf-8;base64,';
|
||||||
@@ -46,72 +46,7 @@ type SourceMap = {
|
|||||||
mappings: string;
|
mappings: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function extract_css(client_result: CompileResult, components: PageComponent[], dirs: Dirs) {
|
function get_css_from_modules(modules: string[], css_map: Map<string, string>, dirs: Dirs) {
|
||||||
const result: {
|
|
||||||
main: string | null;
|
|
||||||
chunks: Record<string, string[]>
|
|
||||||
} = {
|
|
||||||
main: null,
|
|
||||||
chunks: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!client_result.css_files) return; // Rollup-only for now
|
|
||||||
|
|
||||||
const unaccounted_for = new Set();
|
|
||||||
|
|
||||||
const css_map = new Map();
|
|
||||||
client_result.css_files.forEach(css => {
|
|
||||||
unaccounted_for.add(css.id);
|
|
||||||
css_map.set(css.id, css.code);
|
|
||||||
});
|
|
||||||
|
|
||||||
const chunk_map = new Map();
|
|
||||||
client_result.chunks.forEach(chunk => {
|
|
||||||
chunk_map.set(chunk.file, chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
const chunks_with_css = new Set();
|
|
||||||
|
|
||||||
// figure out which chunks belong to which components...
|
|
||||||
const component_owners = new Map();
|
|
||||||
client_result.chunks.forEach(chunk => {
|
|
||||||
chunk.modules.forEach(module => {
|
|
||||||
const component = posixify(path.relative(dirs.routes, module));
|
|
||||||
component_owners.set(component, chunk);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const chunks_depended_upon_by_component = new Map();
|
|
||||||
|
|
||||||
// ...so we can figure out which chunks don't belong
|
|
||||||
components.forEach(component => {
|
|
||||||
const chunk = component_owners.get(component.file);
|
|
||||||
if (!chunk) {
|
|
||||||
// this should never happen!
|
|
||||||
throw new Error(`Could not find chunk that owns ${component.file}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunks = new Set([chunk]);
|
|
||||||
chunks.forEach(chunk => {
|
|
||||||
chunk.imports.forEach((file: string) => {
|
|
||||||
const chunk = chunk_map.get(file);
|
|
||||||
if (chunk) chunks.add(chunk);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chunks.forEach(chunk => {
|
|
||||||
chunk.modules.forEach((module: string) => {
|
|
||||||
unaccounted_for.delete(module);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chunks_depended_upon_by_component.set(
|
|
||||||
component,
|
|
||||||
chunks
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function get_css_from_modules(modules: string[]) {
|
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
const mappings: number[][][] = [];
|
const mappings: number[][][] = [];
|
||||||
|
|
||||||
@@ -168,18 +103,43 @@ export default function extract_css(client_result: CompileResult, components: Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function extract_css(client_result: CompileResult, components: PageComponent[], dirs: Dirs) {
|
||||||
|
const result: {
|
||||||
|
main: string | null;
|
||||||
|
chunks: Record<string, string[]>
|
||||||
|
} = {
|
||||||
|
main: null,
|
||||||
|
chunks: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!client_result.css_files) return; // Rollup-only for now
|
||||||
|
|
||||||
let asset_dir = `${dirs.dest}/client`;
|
let asset_dir = `${dirs.dest}/client`;
|
||||||
if (process.env.SAPPER_LEGACY_BUILD) asset_dir += '/legacy';
|
if (process.env.SAPPER_LEGACY_BUILD) asset_dir += '/legacy';
|
||||||
|
|
||||||
const replacements = new Map();
|
const unclaimed = new Set(client_result.css_files.map(x => x.id));
|
||||||
|
|
||||||
chunks_depended_upon_by_component.forEach((chunks, component) => {
|
const lookup = new Map();
|
||||||
const chunks_with_css = Array.from(chunks).filter(chunk => {
|
client_result.chunks.forEach(chunk => {
|
||||||
const css = get_css_from_modules(chunk.modules);
|
lookup.set(chunk.file, chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
const css_map = new Map();
|
||||||
|
client_result.css_files.forEach(css_module => {
|
||||||
|
css_map.set(css_module.id, css_module.code);
|
||||||
|
});
|
||||||
|
|
||||||
|
const chunks_with_css = new Set();
|
||||||
|
|
||||||
|
// concatenate and emit CSS
|
||||||
|
client_result.chunks.forEach(chunk => {
|
||||||
|
const css_modules = chunk.modules.filter(m => css_map.has(m));
|
||||||
|
if (!css_modules.length) return;
|
||||||
|
|
||||||
|
const css = get_css_from_modules(css_modules, css_map, dirs);
|
||||||
|
|
||||||
if (css) {
|
|
||||||
const { code, map } = css;
|
const { code, map } = css;
|
||||||
|
|
||||||
const output_file_name = chunk.file.replace(/\.js$/, '.css');
|
const output_file_name = chunk.file.replace(/\.js$/, '.css');
|
||||||
@@ -190,18 +150,60 @@ export default function extract_css(client_result: CompileResult, components: Pa
|
|||||||
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=./${output_file_name}.map */`);
|
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=./${output_file_name}.map */`);
|
||||||
fs.writeFileSync(`${asset_dir}/${output_file_name}.map`, JSON.stringify(map, null, ' '));
|
fs.writeFileSync(`${asset_dir}/${output_file_name}.map`, JSON.stringify(map, null, ' '));
|
||||||
|
|
||||||
return true;
|
chunks_with_css.add(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
const entry = path.resolve(dirs.src, 'client.js');
|
||||||
|
const entry_chunk = client_result.chunks.find(chunk => chunk.modules.indexOf(entry) !== -1);
|
||||||
|
|
||||||
|
const entry_chunk_dependencies: Set<Chunk> = new Set([entry_chunk]);
|
||||||
|
const entry_css_modules: string[] = [];
|
||||||
|
|
||||||
|
// recursively find the chunks this component depends on
|
||||||
|
entry_chunk_dependencies.forEach(chunk => {
|
||||||
|
chunk.imports.forEach(file => {
|
||||||
|
entry_chunk_dependencies.add(lookup.get(file));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chunks_with_css.has(chunk)) {
|
||||||
|
chunk.modules.forEach(file => {
|
||||||
|
unclaimed.delete(file);
|
||||||
|
if (css_map.has(file)) {
|
||||||
|
entry_css_modules.push(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const files = chunks_with_css.map(chunk => chunk.file.replace(/\.js$/, '.css'));
|
// figure out which (css-having) chunks each component depends on
|
||||||
|
components.forEach(component => {
|
||||||
|
const resolved = path.resolve(dirs.routes, component.file);
|
||||||
|
const chunk: Chunk = client_result.chunks.find(chunk => chunk.modules.indexOf(resolved) !== -1);
|
||||||
|
|
||||||
replacements.set(
|
if (!chunk) {
|
||||||
component.file,
|
// this should never happen!
|
||||||
files
|
throw new Error(`Could not find chunk that owns ${component.file}`);
|
||||||
);
|
}
|
||||||
|
|
||||||
result.chunks[component.file] = files;
|
const chunk_dependencies: Set<Chunk> = new Set([chunk]);
|
||||||
|
const css_dependencies: string[] = [];
|
||||||
|
|
||||||
|
// recursively find the chunks this component depends on
|
||||||
|
chunk_dependencies.forEach(chunk => {
|
||||||
|
chunk.imports.forEach(file => {
|
||||||
|
chunk_dependencies.add(lookup.get(file));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chunks_with_css.has(chunk)) {
|
||||||
|
css_dependencies.push(chunk.file.replace(/\.js$/, '.css'));
|
||||||
|
|
||||||
|
chunk.modules.forEach(file => {
|
||||||
|
unclaimed.delete(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result.chunks[component.file] = css_dependencies;
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.readdirSync(asset_dir).forEach(file => {
|
fs.readdirSync(asset_dir).forEach(file => {
|
||||||
@@ -210,13 +212,17 @@ export default function extract_css(client_result: CompileResult, components: Pa
|
|||||||
const source = fs.readFileSync(`${asset_dir}/${file}`, 'utf-8');
|
const source = fs.readFileSync(`${asset_dir}/${file}`, 'utf-8');
|
||||||
|
|
||||||
const replaced = source.replace(/["']__SAPPER_CSS_PLACEHOLDER:(.+?)__["']/g, (m, route) => {
|
const replaced = source.replace(/["']__SAPPER_CSS_PLACEHOLDER:(.+?)__["']/g, (m, route) => {
|
||||||
return JSON.stringify(replacements.get(route));
|
return JSON.stringify(result.chunks[route]);
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(`${asset_dir}/${file}`, replaced);
|
fs.writeFileSync(`${asset_dir}/${file}`, replaced);
|
||||||
});
|
});
|
||||||
|
|
||||||
const leftover = get_css_from_modules(Array.from(unaccounted_for));
|
unclaimed.forEach(file => {
|
||||||
|
entry_css_modules.push(css_map.get(file));
|
||||||
|
});
|
||||||
|
|
||||||
|
const leftover = get_css_from_modules(entry_css_modules, css_map, dirs);
|
||||||
if (leftover) {
|
if (leftover) {
|
||||||
const { code, map } = leftover;
|
const { code, map } = leftover;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user