diff --git a/build-scripts/bundle.cjs b/build-scripts/bundle.cjs index aa84af365cbc..311158c3bfea 100644 --- a/build-scripts/bundle.cjs +++ b/build-scripts/bundle.cjs @@ -1,6 +1,7 @@ const path = require("path"); const env = require("./env.cjs"); const paths = require("./paths.cjs"); +const lightningcss = require("./lightningcss.cjs"); // GitHub base URL to use for production source maps // Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version @@ -69,9 +70,6 @@ module.exports.htmlMinifierOptions = { decodeEntities: true, removeComments: true, removeRedundantAttributes: true, - minifyCSS: { - compatibility: "*,-properties.zeroUnits", - }, }; module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({ @@ -116,8 +114,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ ignoreModuleNotFound: true, }, ], - // Minify template literals for production - isProdBuild && [ + [ "template-html-minifier", { modules: { @@ -129,7 +126,10 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ "@polymer/polymer/lib/utils/html-tag": ["html"], }, strictCSS: true, - htmlMinifier: module.exports.htmlMinifierOptions, + htmlMinifier: { + ...module.exports.htmlMinifierOptions, + minifyCSS: lightningcss.getMinifyCSS({ latestBuild, isProdBuild }), + }, failOnError: true, // we can turn this off in case of false positives }, ], diff --git a/build-scripts/gulp/entry-html.js b/build-scripts/gulp/entry-html.js index dd6285c3103a..a7d5dda9f9e7 100644 --- a/build-scripts/gulp/entry-html.js +++ b/build-scripts/gulp/entry-html.js @@ -8,6 +8,7 @@ import path from "path"; import { htmlMinifierOptions, terserOptions } from "../bundle.cjs"; import env from "../env.cjs"; import paths from "../paths.cjs"; +import { getMinifyCSS } from "../lightningcss.cjs"; const renderTemplate = (templateFile, data = {}) => { const compiled = template( @@ -35,6 +36,7 @@ const minifyHtml = (content, ext) => { return minify(begTag + content + endTag, { ...htmlMinifierOptions, conservativeCollapse: false, + minifyCSS: getMinifyCSS({ latestBuild: false, isProdBuild: true }), // Transpile and minify minifyJS: terserOptions({ latestBuild: false, // Shared scripts should be ES5 isTestBuild: true, // Don't need source maps diff --git a/build-scripts/lightningcss.cjs b/build-scripts/lightningcss.cjs new file mode 100644 index 000000000000..a90828334713 --- /dev/null +++ b/build-scripts/lightningcss.cjs @@ -0,0 +1,74 @@ +const CleanCSS = require("clean-css"); +const browserslist = require("browserslist"); +const { transform, browserslistToTargets, Features } = require("lightningcss"); + +const cleanCSS = new CleanCSS({ compatibility: "*,-properties.zeroUnits" }); +const decoder = new TextDecoder(); +function wrapCSS(text, type) { + if (type === "inline") return `#a{${text}}`; + if (type === "media") return `@media ${text}{#a{top:0}}`; + return text; +} +function unwrapCSS(text, type) { + if (type === "inline") return text.match(/^#a ?\{([\s\S]*)\}$/m)[1]; + if (type === "media") + return text.match(/^@media ?([\s\S]*?) ?{[\s\S]*}$/m)[1]; + return text; +} + +module.exports.getMinifyCSS = ({ latestBuild, isProdBuild }) => { + const cssTargets = browserslistToTargets( + browserslist( + browserslist.loadConfig({ + path: ".", + env: latestBuild ? "modern" : "legacy", + }), + { throwOnMissing: true, mobileToDesktop: true } + ) + ); + return (text, type) => { + if (!text) return text; + const input = wrapCSS(text, type); + if ( + !text.includes("babel-plugin-template-html-minifier") && + !text.includes("@apply") + ) { + const { code, warnings: ws } = transform({ + filename: "style.css", + code: Buffer.from(input), + minify: isProdBuild, + targets: cssTargets, + exclude: Features.DirSelector, + }); + const warnings = ws.filter( + (w) => w.message !== "Unknown at rule: @apply" + ); + if (warnings.length > 0) { + console.warn("[LCSS] Warnings while transforming CSS:", ...warnings); + } + const lcss = decoder.decode(code); + try { + return unwrapCSS(lcss, type); + } catch (e) { + console.error("[LCSS] Invalid output", { text, type, output: lcss, e }); + return text; + } + } + if (isProdBuild) { + const { styles: ccss, errors, warnings } = cleanCSS.minify(input); + if (errors.length > 0) { + console.error("[CCSS] Errors while transforming CSS:", ...errors); + } + if (warnings.length > 0) { + console.warn("[CCSS] Warnings while transforming CSS:", ...warnings); + } + try { + return unwrapCSS(ccss, type); + } catch (e) { + console.error("[CCSS] Invalid output", { text, type, output: ccss, e }); + return text; + } + } + return text; + }; +}; diff --git a/package.json b/package.json index 08fcba83b24d..e0f5ccb2138e 100644 --- a/package.json +++ b/package.json @@ -221,6 +221,7 @@ "husky": "8.0.3", "instant-mocha": "1.5.2", "jszip": "3.10.1", + "lightningcss": "1.22.0", "lint-staged": "14.0.1", "lit-analyzer": "2.0.0-pre.3", "lodash.template": "4.5.0", diff --git a/yarn.lock b/yarn.lock index 2a9cb5c3a2e1..99d7559a1f9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7515,6 +7515,15 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^1.0.3": + version: 1.0.3 + resolution: "detect-libc@npm:1.0.3" + bin: + detect-libc: ./bin/detect-libc.js + checksum: daaaed925ffa7889bd91d56e9624e6c8033911bb60f3a50a74a87500680652969dbaab9526d1e200a4c94acf80fc862a22131841145a0a8482d60a99c24f4a3e + languageName: node + linkType: hard + "detect-node@npm:^2.0.4": version: 2.1.0 resolution: "detect-node@npm:2.1.0" @@ -9798,6 +9807,7 @@ __metadata: jszip: 3.10.1 leaflet: 1.9.4 leaflet-draw: 1.0.4 + lightningcss: 1.22.0 lint-staged: 14.0.1 lit: 2.8.0 lit-analyzer: 2.0.0-pre.3 @@ -11382,6 +11392,106 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-arm64@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-darwin-arm64@npm:1.22.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-darwin-x64@npm:1.22.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-freebsd-x64@npm:1.22.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.22.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.22.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.22.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.22.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-linux-x64-musl@npm:1.22.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.22.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:1.22.0": + version: 1.22.0 + resolution: "lightningcss@npm:1.22.0" + dependencies: + detect-libc: ^1.0.3 + lightningcss-darwin-arm64: 1.22.0 + lightningcss-darwin-x64: 1.22.0 + lightningcss-freebsd-x64: 1.22.0 + lightningcss-linux-arm-gnueabihf: 1.22.0 + lightningcss-linux-arm64-gnu: 1.22.0 + lightningcss-linux-arm64-musl: 1.22.0 + lightningcss-linux-x64-gnu: 1.22.0 + lightningcss-linux-x64-musl: 1.22.0 + lightningcss-win32-x64-msvc: 1.22.0 + dependenciesMeta: + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 6b9a04846243a2161ac12ee098f9c2143a1a06fb683228ef0433473257751a709b0bafa195efa8d3d8f1556ca60c54f5434caeb172874a8daced552dedcbed93 + languageName: node + linkType: hard + "lilconfig@npm:2.1.0": version: 2.1.0 resolution: "lilconfig@npm:2.1.0"