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:
Rich Harris
2018-10-16 08:38:20 -04:00
committed by GitHub

View File

@@ -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;