ããã«ã¡ã¯ãããã³ãã¨ã³ãã¨ãã¹ãã¼ããã¼ã ã®ç©´äºï¼@pirosikickï¼ã§ããç¦å²¡å¨ä½ã§ãæ®æ®µã¯ç¦å²¡ã®weworkã§åãã¦ãã¾ããä»ã®ã¡ã³ãã¼ã¯çãæ±äº¬ã«å± ã¦ãªã¢ã¼ãã§ä»äºããã¦ãã¾ãããã¢ãã§ããããéçºãã¦ãã¾ãããweworkãå¿«é©ããã¦ãæ¯æ¥æ¥½ããã§ãï¼
ããã³ãã¨ã³ãã¨ãã¹ãã¼ããã¼ã ã§ã¯ããµã¤ãã¦ãºã®åãããã¯ããæ±ããWebããã³ãã¨ã³ãã®èª²é¡ã解決ããã®ãä»äºã®ä¸ã¤ã§ãã
æè¿ã®åãçµã¿ã¨ãã¦ãPuppeteerã§ä¸è¦ãªCSSãæ¶ããäºä¾ãç´¹ä»ãã¾ãã
ãã®ããã°ã¯ã6/19ã«ç¦å²¡ã§éå¬ãããGoogle I/O '19ã®Webãã¾ã¨ããä¼ãã§ç»å£ããã¨ãã®å 容ã詳細ã«èª¬æãã¤ã¤ãã¢ãããã¼ãããé¨åãããã®ã§ãçºè¡¨è¦ãããã¹ã©ã¤ãè¦ããã¨ããæ¹ãè¦ã¦ããã ãã¾ãã¨å¹¸ãã§ãã
ãã£ãã
ã¨ãããããã¯ãã®CSSãstyled-componentsã«ç§»è¡ãã¦ããã®ã§ããããã®ä¸ã®ä¸ã¤ã®CSSãã¡ã¤ã«ãã
- 1ä¸è¡ãã
- ã©ãã§ä½¿ããã¦ããã®ãä¸æãªã¹ã¿ã¤ã«ãå¤ããã
- 大éã®ã»ã¬ã¯ã¿ã«å¯¾ãã¦ã¹ã¿ã¤ã«ãå½ã¦ã¦ããã«ã¼ã«ãå¤ããã移è¡ä½æ¥ã®ã³ã¹ããé«ã
ãªã©ãå¤ãã®èª²é¡ãæ±ãã¦ãããã©ããã£ã¦ç§»è¡ããã°ããã®ãã¨é ãæ±ãã¦ãã¾ããããããªæããã¾ãã¾è¦³ãGoogle I/O '19ã®Puppeteerã«é¢ããã»ãã·ã§ã³ã§CSSã®ã«ãã¬ãã¸ãåå¾ã§ãããã¨ãç¥ããããã®æ©è½ã使ã£ã¦ä¸è¦ãªCSSãæ´ãåºãã°ãã¡ãã£ã¨ã¯æ¥½ãã§ããã®ã§ã¯ï¼ãã¨æã£ãã®ããã£ããã§ãã
çµæã¯ãã¾ãæå¾ ããPuppeteerã使ãå§ããã®ã§ãããçµæã¨ãã¦1ä¸è¡ãã£ãCSSãã¡ã¤ã«ã¯7%ããããã使ããã¦ããªããã¨ãå¤æããæ³å®ãããå¤§å¹ ã«ä¸è¦ãªã¹ã¿ã¤ã«ãåé¤ã§ãã¾ããããã®ãããã£ã¦ãstyled-componentsã¸ã®ç§»è¡ä½æ¥ãç¡äºçµãããã¨ãã§ãã¾ããï¼
ãããããPuppeteerã¨ã¯ä½ãï¼ãããåå¾ããã«ãã¬ãã¸ããã¨ã«CSSãåé¤ããæ¹æ³ã«ã¤ãã¦è§£èª¬ãã¦ããã¾ãã
Puppeteerã¨ã¯ï¼
Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol.
Puppeteerã¯ãChromeãæä½ããAPIãæä¾ããNode.jsã®ã©ã¤ãã©ãªã§ããChromeã®Dev Toolsã®ãããã³ã«ãä»ãã¦ãChromeãæä½ãã¾ãã使ãæ¹ã¯ç°¡åã§ãnpm install puppeteer
ã§Puppeteerãã¤ã³ã¹ãã¼ã«ããã³ã¼ããæ¸ãã ãã§ãã
const puppeteer = require("puppeteer"); (async () => { // ãã©ã¦ã¶ã®èµ·å const browser = await puppeteer.launch(); const page = await browser.newPage(); // Googleã«ç§»å await page.goto("https://google.com"); })();
ãã£ã¨è©³ããç¥ãããå ´åã¯å ¬å¼ããã¥ã¡ã³ããããã§ãããGoogle I/O '19ã®Puppeteerã®ã»ãã·ã§ã³ãããããããã®ã§ããããã§ãã
Puppeteerã§CSSã®ã«ãã¬ãã¸ãåã
Puppeteerã§CSSã®ã«ãã¬ãã¸ãåéããã«ã¯ãã¾ãpage.coverage.startCSSCoverage()
ã§ã«ãã¬ãã¸ã®åéãéå§ãã¾ããstartCSSCoverage
å¼ã³åºãæã®ãªãã·ã§ã³ã§ãresetOnNavigation
ãfalse
ã«ããªãã¨ããã¼ã¸é·ç§»ãçºçãããã³ã«ã«ãã¬ãã¸ããªã»ããããã¦ãã¾ãã®ã§æ³¨æãã¾ãããããã®å¾ãpage.goto(...)
ãªã©ã§ãã©ã¦ã¶ãæä½ããã«ãã¬ãã¸ãåéããããã¼ã¸ã表示ããã¾ããåéããããã¼ã¸ã表示ãçµããããpage.coverage.stopCSSCoverage()
ã§ã«ãã¬ãã¸ã®åéãçµããã¾ããstopCSSCoverage
ã¯ãstartCSSCoverage
å¼ã³åºãããstopCSSCoverage
å¼ã³åºãã¾ã§ã«åéããã«ãã¬ãã¸ãè¿ãã¾ãã
// ã«ãã¬ãã¸åéãéå§ await page.coverage.startCSSCoverage({ // ãã¼ã¸é·ç§»ã®ãã³ã«ã«ãã¬ãã¸ããªã»ãããããã®ãé²ã resetOnNavigation: false }); // ãã©ã¦ã¶ãæä½ãã«ãã¬ãã¸ãåéããããã¼ã¸ã表示ãã await page.goto('â¦'); ⦠// ã«ãã¬ãã¸åéãçµäº // startCSSCoverageããstopCSSCoverageã¾ã§ã®éã«åéããã«ãã¬ãã¸ãè¿ã const coverage = await page.coverage.stopCSSCoverage();
ã¡ãªã¿ã«ãJavaScriptã®ã«ãã¬ãã¸ã¯page.coverage.startJSCoverage()
, page.coverage.stopJSCoverage()
ã§åæ§ã«åå¾ã§ãã¾ãã
ã«ãã¬ãã¸ãè¦è¦çã«ç¢ºèªãã
åéããã«ãã¬ãã¸ãè¦è¦çã«ç¢ºèªããã«ã¯ãpuppeteer-to-istanbulã§ã«ãã¬ãã¸ãistanbulã®ãã©ã¼ãããã§åºåããistanbulã®CLIãã¼ã«ã§ããnycã§ã¬ãã¼ããåºåãã¾ãã
# puppeteer-to-istanbulã®ã¤ã³ã¹ãã¼ã« $ npm install --save puppeteer-to-istanbul
const puppeteer = require('puppeteer'); const ptoi = require('puppeteer-to-istanbul'); â¦å²æ⦠// ã«ãã¬ãã¸ãåé const coverage = await page.coverage.stopCSSCoverage(); // ã«ãã¬ãã¸ãistanbulå½¢å¼ã«å¤æï¼.nyc_output以ä¸ã«åºå ptoi.write(coverage);
# HTMLå½¢å¼ã§ã¬ãã¼ããåºå $ npx nyc report --reporter=html # ãã©ã¦ã¶ã§è¡¨ç¤ºããï¼macã®å ´åï¼ $ open coverage/index.html
HTMLå½¢å¼ã§åºåããå ´åã以ä¸ã®ãããªã¬ãã¼ããåºåããã¾ãã赤ã表示ããã¦ããé¨åãã«ãã¬ãã¸ããªãé¨åã§ãã
åã£ãã«ãã¬ãã¸ãå ã«CSSããä¸è¦ãªé¨åãåé¤ãã
page.coverage.stopCSSCoverage()
ãè¿ãã«ãã¬ãã¸æ
å ±ã¯ä»¥ä¸ã®ãããªæ§é ã«ãªã£ã¦ãã¾ãã
[ { url: "ãã¡ã¤ã«ã®URL", text: "ãã¡ã¤ã«ã®ä¸èº«", ranges: [ { start: "ã«ãã¬ãã¸éå§ä½ç½®", end: "ã«ãã¬ãã¸çµäºä½ç½®" }, { start: "ã«ãã¬ãã¸éå§ä½ç½®", end: "ã«ãã¬ãã¸çµäºä½ç½®" }, ⦠] }, ⦠]
ãã£ã¦ã以ä¸ã®ããã«range
ãå
ã«text
ããã«ãã¬ãã¸ãããã³ã¼ãã®ã¿æãåºãã°ã使ããã¦ããªãã³ã¼ããé¤å¤ãããCSSãçæã§ããããªæãããã¾ãã
const coverage = await page.coverage.stopCoverage(); const coolCSS = coverage.map(({ url, text, ranges }) => { // rangesãå ã«ä½¿ããã¦ããCSSã ããæãåºã const css = ranges .map(({ start, end }) => text.substring(start, end)) .join(""); return { url, css }; }); console.log("I got cool css!", coolCss);
ãããã®æ¹æ³ã«ã¯ããã¤ãè½ã¨ãç©´ãããã¾ãã
è½ã¨ãç©´ â : @font-face
ã«ã«ãã¬ãã¸ãçºçããªã
Puppeteerã®åºåããã«ãã¬ãã¸ã«ã¯ã@font-face { ⦠}
ã®ã«ãã¬ãã¸ãçºçãã¾ããããã£ã¦ãã«ãã¬ãã¸ããCSSä¸ã®@font-face {â¦}
ãå¿
è¦ãªãã®ãªã®ãå¤æãããã¨ãã§ãã¾ããããã®ããã表示崩ããé²ãããã«ã¯ã@font-face
ã¯ãæ¶ããã«å¸¸ã«æ®ãå¿
è¦ãããã¾ãã
ã¡ãªã¿ã«ãPuppeteerã®Github ã«ãã®æåã«é¢ãã issueãç»é²ããã¦ãã¾ãããéçºãã¼ã ããåå¿ããªããç¾æç¹ã§ã¯ä»æ§ãªã®ããã°ãªã®ãã¯ãããã¾ããã
è½ã¨ãç©´ â¡: @media
ã«ã«ãã¬ãã¸ãçºçããªã
@media
ã使ãã¨ã¹ã¿ã¤ã«ãé©å¿ãããæ¡ä»¶ãå®ç¾©ã§ãã¾ããã¬ã¹ãã³ã·ã対å¿ã§ã馴æã¿ã®è¨æ³ã§ãã
/* å¼ç¨å : http://www.tohoho-web.com/css/rule/media.htm */ /* å¹ ã 767px 以ä¸ã§ããã° */ @media (max-width: 767px) { ...; } /* å¹ ã 768px 以ä¸ã§ããã° */ @media (min-width: 768px) { ...; } /* å¹ ã 768pxä»¥ä¸ 1200px以ä¸ã§ããã° */ @media (min-width: 768px) and (max-width: 1200px) { ...; } /* ã«ã©ã¼ãã£ã¹ãã¬ã¤ã¾ãã¯ã¢ãã¯ãå°å·ã§ããã° */ @media screen and (color), print and (monochrome) { ...; }
Puppeteerã§ã¯ã@media ⦠{ ⦠}
å
ã®ã¹ã¿ã¤ã«ã¯ã«ãã¬ãã¸ãçºçãã¾ãããéå§ã®@media ⦠{
ã¨çµäºã®}
ã«ã«ãã¬ãã¸ãçºçãã¾ããã
ãã£ã¦ãä¸èº«ã®ã¹ã¿ã¤ã«ã«ã«ãã¬ãã¸ããã@media
ã ãæ¶ããã«æ®ãã¨ããããè¤éãªå¦çãå¿
è¦ã§ãã
AST ã使ã£ã¦ã³ã¼ããæãåºã
ãã®ãããªè½ã¨ãç©´ããããããæåã«ç´¹ä»ããæ¹æ³ï¼ã«ãã¬ãã¸ãå ã«substringé¢æ°ã使ã£ã¦å¿ è¦ãªCSSã ããæãåºãï¼ã§ã¯ãå¿ è¦ãªCSSã¾ã§åé¤ããã¦ãã¾ãã¾ããã¾ããè½ã¨ãç©´â¡ãæååãã¼ã¹ã§è§£æ±ºãããã¨ããã¨ããªããªãè¤éãªå¦çãæ¸ãå¿ è¦ãããã¾ããããã§ãããããªã®ããASTã使ã£ã¦å¦çããæ¹æ³ã§ãã
ASTã¨ã¯ï¼
ASTã¨ã¯Abstract Syntax Treeã®ç¥ã§ãæ¥æ¬èªã§ããã¨æ½è±¡æ§ææ¨ã§ãã½ã¼ã¹ã³ã¼ããæ¨æ§é ã§è¡¨ç¾ãããã®ã§ããWebããã³ãã¨ã³ãã§ã¯æ¬ ãããªããã¼ã«ã«ãªã£ã¦ããBabelãwebpackãTypeScriptãªã©ãå é¨ã§ASTãå©ç¨ãã¦ãããWebããã³ãã¨ã³ãã«ããã¦ASTã¯ã ãã¶é¦´æã¿æ·±ããã®ã«ãªã£ã¦ããã®ã§ã¯ãªãã§ããããï¼CSSã ã¨PostCSSãæåãªã®ã§ãPostCSSã®ASTã使ã£ã¦èª¬æãã¦ããã¾ãã以ä¸ã®CSSãPostCSSã§ASTã«å¤æããã¨å³ã®ãããªæ¨æ§é ã«ãªãã¾ãã
@media screen and (min-width: 480px) { body { background-color: lightgreen; } } #main { border: 1px solid black; } ul li { padding: 5px; }
AST Explorerã使ãã¨ãã½ã¼ã¹ã³ã¼ããã©ã®ãããªASTã«å¤æãããããªã¢ã«ã¿ã¤ã ãã¤è¦è¦çã«ç¢ºèªãããã¨ãã§ãã¾ãã
ASTã使ã£ã¦ã½ã¼ã¹ã³ã¼ããç·¨éããå©ç¹ã¯ãã·ã³ã¿ãã¯ã¹ã¨ã©ã¼ãå«ãã½ã¼ã¹ã³ã¼ããçæããå¯è½æ§ããªããã¨ã¨ãç·¨éãããã¸ãã¯ãã·ã³ãã«ã«ãªãç¹ã§ãã ã½ã¼ã¹ã³ã¼ããæååã¨ãã¦æ±ã£ã¦ç·¨éããå ´åãç·¨éãããã¸ãã¯ã«èª¤ããããã¨ã·ã³ã¿ãã¯ã¹ã¨ã©ã¼ã«ãªããããªã½ã¼ã¹ã³ã¼ããçæãããæããããã¾ããä¸æ¹ãASTã«å¤æãã¦ç·¨éããã°ãASTããã½ã¼ã¹ã³ã¼ãã«æ»ãã¨ãã«ã¨ã©ã¼ã«ãªãã®ã§ã·ã³ã¿ãã¯ã¹ã¨ã©ã¼ã«ãªããããªã³ã¼ãã¯çæããã¾ãããã¾ããç·¨éã®ãã¸ãã¯ãã·ã³ãã«ã«ãªãã¾ããä¾ãã°ãä¸è¨ã®CSSã§ã#mainã®ã¹ã¿ã¤ã«ãæ¶ããããã¨ããå ´åã¯ãå³ã®çãä¸ã®Ruleãæ¨æ§é ããåé¤ããã ãã§ãã
PostCSSãåºåããASTã¯ãå®éã«ã¯JSã®ãªãã¸ã§ã¯ãã§è¡¨ç¾ããã¾ããASTã®æ¨æ§é ã¸ã®è¿½å ã»åé¤ãªã©ã®æä½ã¯ãåãªãã¸ã§ã¯ãã«é¢æ°ãå®ç¾©ããã¦ãã¾ããã§ã¯ãPostCSSã®ASTã使ã£ã¦ãã«ãã¬ãã¸ã®ãªãã¹ã¿ã¤ã«ãåé¤ããã³ã¼ãã解説ãã¦ããã¾ãã
0. PostCSSã®ã¤ã³ã¹ãã¼ã«
ã¾ããPostCSSãnpmã§ã¤ã³ã¹ãã¼ã«ãã¾ãã
# PostCSSã®ã¤ã³ã¹ãã¼ã« # é©å®--saveã--save-devãä»ãã $ npm install postcss
1. CSSããASTãçæãã
CSSã®ã½ã¼ã¹ã³ã¼ãããASTãçæãã¾ããpostcss.parser
é¢æ°ã«CSSãæååã§æ¸¡ãã¨ãASTã®Rootï¼æ¨æ§é ã®æ ¹ï¼ãè¿ãã¾ããã¡ãªã¿ã«ã渡ããCSSã«ã·ã³ã¿ãã¯ã¹ã¨ã©ã¼ãããã¨ã¨ã©ã¼ãæãã¾ãã
const postcss = require("postcss"); /** * Puppeteerã®CSSã«ãã¬ãã¸ããä¸è¦ãªã¹ã¿ã¤ã«ãåé¤ããCSSãçæ * * @param {Object} coverage - stopCSSCoverageãè¿ãé åã®è¦ç´ */ const removeUnusedCSS = coverage => { // CSSããASTãçæ const root = postcss.parser(coverage.text); ⦠};
2. ASTãæ¢ç´¢ãã¦åé¤å¯¾è±¡ã®ãã¼ããè¦ã¤ãã
次ã«ãrootã«çãã¦ããwalké¢æ°ã使ã£ã¦æ¨æ§é ãæ¢ç´¢ããåé¤å¯¾è±¡ã®ãã¼ããè¦ã¤ãã¾ãã
// ASTã®æ¢ç´¢ root.walk(node => { // åé¤å¯¾è±¡ãï¼ if (isNodeUnneeded(node)) { // åé¤å¯¾è±¡ãªãASTããåé¤ãã node.remove(); } });
3. ãã¼ããåé¤å¯¾è±¡ãå¤å®ãã
ãã¼ããåé¤å¯¾è±¡ãå¤å®ããisNodeUnneeded
é¢æ°ãå®è£
ãã¾ãããã¼ããåé¤ãããã¯ä»¥ä¸ã®ãããªæ¡ä»¶ã§ããã³ã¡ã³ããåé¤ãããã¯å¥½ã¿ã§ãããä»åã¯ããããªãé«ã¿ã¸ï¼ä½¿ããã¦ããªãã»ã¬ã¯ã¿è¨è¿°ãé¤å»ãããã®ç« ã§å¿
è¦ã«ãªãã®ã§ãã®ã¾ã¾æ®ãã¾ããPuppeteerã®CSSã®ã«ãã¬ãã¸ã¯ã«ã¼ã«åä½ã«ããçºçããªãã®ã§ãDeclarationãåé¤å¯¾è±¡ã¨ãããç¡è¦ãã¾ãããã®ä»ã®ãã¼ãã«é¢ãã¦ã¯ãã«ãã¬ãã¸ã®æç¡ãå¤æãã¦æ±ºãã¾ãã
- 以ä¸ã®ãã¼ãã¯åé¤ããªã
- Root
@font-face
ï¼è½ã¨ãç©´â ï¼- ã³ã¡ã³ã
- Declaration
- ãã®ä»ã®ãã¼ãã¯ãã«ãã¬ãã¸ãããã°åé¤ããªã
ãã¼ãã®ç¨®é¡ã¯node.type
ã§åå¾ã§ããRootã®å ´åã¯"root"
ã ã³ã¡ã³ãã®å ´åã¯"comment"
ã¨ããæãã§æååãå
¥ã£ã¦ãã¾ãããã¼ãã@font-face
ãã©ããã¯ãnode.type
ã"atrule"
ã§ãnode.name
ã"font-face"
ãã§å¤å®ãã¾ãã
// ãã¼ããåé¤å¯¾è±¡ãå¤å® const isNodeUnneeded = (node, coverage) => { // Root, Comment, Declarationã¯åé¤ããªã if (['root', 'comment', 'decl'].includes(node.type)) { return false; } // @font-faceã¯åé¤ããªã if (node.type === 'atrule' && node.name === 'font-face') { return false; } // ãã®ä»ï¼ã«ãã¬ãã¸ãç¡ããã°åé¤ â¦ };
ãã¼ãã®ã«ãã¬ãã¸æç¡ã¯ããã¼ãã®ã½ã¼ã¹ã³ã¼ãä¸ã®ä½ç½®ãã«ãã¬ãã¸ã®ç¯å²ã«åå¨ãããã§å¤å®ãã¾ãããã¼ãã®ã½ã¼ã¹ã³ã¼ãä¸ã®ä½ç½®ã¯node.source
ããåå¾ãã¾ãï¼â ï¼ãnode.source.start
ãéå§ä½ç½®ãnode.source.end
ãçµäºä½ç½®ã§ãã
node.source
ã¯è¡çªå·ã»åçªå·ãcoverage.ranges
ã¯æååä½ç½®ã«ãªã£ã¦ããããã®ã¾ã¾æ¯è¼ã§ããªããããnode.source
ãæååä½ç½®ã«å¤æãã¾ãï¼â¡ï¼ã
ãã¨ã¯ãnode.source
ã¨ç¯å²ã被ã£ã¦ããè¦ç´ ãcoverage.ranges
å
ã«ãããæ¢ãã¾ãï¼â¢ï¼ãisNodeUnneeded
é¢æ°ãªã®ã§ãã«ãã¬ãã¸ãç¡ããã°trueãè¿ãã¦ãã¾ãã
⦠// ãã®ä»ï¼ã«ãã¬ãã¸ãç¡ããã°åé¤ // â ãã¼ãã®ã½ã¼ã¹ä¸ã§ã®éå§ã»çµäºä½ç½®ï¼è¡åçªå·ï¼ const { start, end } = node.source; // â¡è¡åçªå·ãæååä½ç½®ï¼0 ~ ã½ã¼ã¹ã³ã¼ãæåå.length - 1ï¼ã«å¤æ const startIndex = lineColumnToIndex(coverage, start.line, start.column); const endIndex = lineColumnToIndex(coverage, end.line, end.column); // â¢ãã¼ãã®ã½ã¼ã¹ã³ã¼ãä¸ã®ç¯å²ãå«ãã«ãã¬ãã¸ãæ¢ã const covered = coverage.ranges.find( range => !(startIndex >= range.end || endIndex < range.start) ); // ã«ãã¬ãã¸ãè¦ã¤ãããªããã°ãtrueãè¿ã return typeof covered === "undefined" }
4. ASTããã½ã¼ã¹ã³ã¼ããçæãã
1~3ã®å¦çã§ãASTããä¸è¦ãªãã¼ãããªããªãã¾ãããæå¾ã«å¦çå¾ã®ASTããCSSãçæãã¾ããASTããCSSãçæããã«ã¯ãroot.toString()
ãå®è¡ãã¾ãã
// ä¸è¦ãªãã¼ããåé¤ãããASTããCSSãçæ return root.toString();
ã¡ãªã¿ã«ãRoot以å¤ã®ãã¼ããtoString()
ãå®è¡ããã¨ããã®ãã¼ãã表ãã½ã¼ã¹ã³ã¼ããè¿ãã¾ãã
ã³ã¼ãã®å®å
¨çã¯ä»¥ä¸ã«ç½®ãã¦ãã¾ããå²æããlineColumnToIndex
é¢æ°ã®å®è£
ãªã©ãè¦ã¦ã¿ããå ´åã¯ãã¡ãããã©ããã
ãããªãé«ã¿ã¸ï¼ä½¿ããã¦ããªãã»ã¬ã¯ã¿è¨è¿°ãé¤å»ãã
Puppeteerã®CSSã®ã«ãã¬ãã¸ã¯ãCSSã®ã«ã¼ã«ãã¨ã«çºçãã¾ãã
/* ã«ã¼ã« = ã»ã¬ã¯ã¿ï¼h1ï¼ï¼å®£è¨ãããã¯ï¼color: red;â¦ï¼*/ h1 { color: red; font-size: 12px; }
ä¾ãã°ã以ä¸ã®ãããªh1ãh6ã«ã¹ã¿ã¤ã«ã宣è¨ãã¦ããã«ã¼ã«ãããã¨ãã«ãå®éã«ã¯h1ãã使ããã¦ããªãå ´åã§ããã«ã¼ã«å ¨ä½ã«ã«ãã¬ãã¸ãçºçãã¾ãã
/* å®éã«ã¯h1ãã使ããã¦ããªãå ´åã§ããã«ã¼ã«å ¨ä½ã«ã«ãã¬ãã¸ãçºç */ h1, h2, h3, h4, h5, h6 { ... }
ä»åã対å¿ãè¡ã£ãCSSã«ã¯ä»¥ä¸ã®ãããªå¤ãã®ã»ã¬ã¯ã¿ãæå®ãã¦ããã«ã¼ã«ãããã¤ãããã¾ããããã®ã¾ã¾ã§ã¯ã«ãã¬ãã¸ããã¨ã«CSSãåé¤ããå¾ã«styled-componentsã«ã¹ã¿ã¤ã«ã移ãã¨ããã»ã¬ã¯ã¿ã使ããã¦ãããç²¾æ»ããæéãçºçã大å¤ã§ãã
/* å®éã«ãã£ãã»ã¬ã¯ã¿ããã£ã±ãããã«ã¼ã«ã®ä¸ä¾ */ .btn, .btn-danger.active, .btn-danger:active, .btn-default.active, .btn-default:active, .btn-info.active, .btn-info:active, .btn-primary.active, .btn-primary:active, .btn-warning.active, .btn-warning:active, .btn.active, .btn:active, .dropdown-menu>.disabled>a:focus, .dropdown-menu>.disabled>a:hover, .form-control, .navbar-toggle, .open>.dropdown-toggle.btn-danger, .open>.dropdown-toggle.btn-default, .open>.dropdown-toggle.btn-info, .open>.dropdown-toggle.btn-primary, .open>.dropdown-toggle.btn-warning { â¦; }
使ããã¦ããªãã»ã¬ã¯ã¿ãåé¤ã§ããããã«ä¸å·¥å¤«ãã¾ããã®ã§ãç´¹ä»ãã¾ãã
1. ã«ã¼ã«ãåã»ã¬ã¯ã¿ãã¨ã«å®ç¾©ãã
Puppeteerãã«ã¼ã«ãã¨ã«ããã«ãã¬ãã¸ãåºããªãé¨åã¯å¤æ´ã§ããªãã®ã§ãã«ã¼ã«ã«è¤æ°ã®ã»ã¬ã¯ã¿ãããå ´åã«åã»ã¬ã¯ã¿ãã¨ã«ã«ã¼ã«ãå®ç¾©ããããã«CSSãå¤æãã¾ãããè¨èã ã¨ãããã¥ããã¨æãã®ã§ãå¤æåå¾ã§èª¬æããã¨ãå¤æåãããã»ã©ã®h1ãh6ã«å¯¾ããã«ã¼ã«ãä¸ã¤ããå ´åãh1~h6ããããã«ã«ã¼ã«ãå®ç¾©ããããã«CSSãå¤æãã¾ãããh1ãh6ã®å ´åãåã宣è¨ãããã¯ããã¤ã«ã¼ã«ã6åã§ããã¨ãããã¨ã§ããã¾ããæé 3ã§å¿
è¦ãªã®ã§ãå¤æå¾ã®ã³ã¼ãã®åå¾ã®è¡ã«ç®å°ã¨ãã¦/* flatten-start */
ã/* flatten-end */
ãä»ãã¾ãã
/* å¤æå */ h1, h2, h3, h4, h5, h6 { ⦠} /* å¤æå¾ */ /* flatten-start */ h1 { ⦠} h2 { ⦠} â¦h3, h4, h5ã¯å²æ⦠h6 { ⦠} /* flatten-end */
ãã¡ãã®ä½æ¥ãpostcssã使ã£ã¦ãå®è£ ãã¾ããã
/** * flatten.js * * ## before * a, b, c { ⦠} * * ## after: * a { ⦠} * b { ⦠} * c { ⦠} */ const fs = require("fs"); const postcss = require("postcss"); const filePath = process.argv[2]; if (typeof filePath !== "string") { console.log("USAGE: node flatten.js path"); process.exit(1); } const css = fs.readFileSync(filePath).toString(); const root = postcss.parse(css); root.walkRules(node => { // ã»ã¬ã¯ã¿ãä¸ã¤ãããªãã«ã¼ã«ã¯ç¡è¦ if (node.selectors.length === 1) { return; } // ã«ã¼ã«ã®ã»ã¬ã¯ã¿ãã¨ã«ã«ã¼ã«ãä½ã const rules = node.selectors.map(selector => postcss.rule({ selector, nodes: node.nodes }) ); // ä½æããã«ã¼ã«ã¨ç®å°ã³ã¡ã³ããæ¿å ¥ node.after([ postcss.comment({ text: "flatten-start" }), ...rules, postcss.comment({ text: "flatten-end" }) ]); // å ã®ã«ã¼ã«ãåé¤ node.remove(); }); console.log(root.toString());
# å®è¡ $ node flatten.js app.css > flatten.app.css
2. 1ã®CSSã®ã«ãã¬ãã¸ãåºããä¸è¦ãªCSSãåé¤
1ã®ä½æ¥ã§çæããCSSã§ã«ãã¬ãã¸ãåãã¨ã使ããã¦ããã»ã¬ã¯ã¿ã»ä½¿ããã¦ããªãã»ã¬ã¯ã¿ãåããã¾ããh1, h2, h3, h4, h5, h6 {â¦}
ã®ãããªã«ã¼ã«ã§ãå®éã¯h1ãã使ããã¦ããªãã£ãå ´åã以ä¸ã®ãããªã«ãã¬ãã¸ã«ãªãã¾ãã
åã£ãã«ãã¬ãã¸ãå ã«CSSãåé¤ããã¨ã使ããã¦ããã»ã¬ã¯ã¿ã®ã«ã¼ã«ã ããæ®ãã¾ãã
3. ã»ã¬ã¯ã¿ãã¨ã«å解ããã«ã¼ã«ãå ã«æ»ã
1ã§æ¬æ¥ä¸ã¤ã®ã«ã¼ã«ãã»ã¬ã¯ã¿ãã¨ã«å解ãããç¶æ
ã«ãªã£ã¦ããã®ã§ãå
ã«æ»ãã¾ãã1ã§ä»ããç®å°ï¼/* flatten-start */
ã/* flatten-end */
ï¼ã®éã«å®ç¾©ãã¦ããã«ã¼ã«ãä¸ã¤ã«ã¾ã¨ãã¾ãã
/** * deflatten.js */ // â¦ãã¡ã¤ã«ããCSSãæååã§èªã¿è¾¼ãå¦çã¯flatten.jsã¨å ¨ãåããªã®ã§å²æ⦠const root = postcss.parse(css); // ã³ã¡ã³ãã®ãã¼ããæ¢ç´¢ root.walkComments(node => { // /* flatten-start */ if (node.text.match(/flatten-start/)) { const selectors = []; let nodes; let current = node.next(); // /* flatten-end */ãè¦ã¤ããã¾ã§æ¢ç´¢ while (!(current.type === "comment" && current.text.match(/flatten-end/))) { // ã»ã¬ã¯ã¿ã¨å®£è¨ãããã¯ãåé selectors.push(current.selector); nodes = current.nodes; const prev = current; current = current.next(); prev.remove(); // å解ããã«ã¼ã«ã¯åé¤ } if (selectors.length) { // ã»ã¬ã¯ã¿ãã¾ã¨ããã«ã¼ã«ãä½æã»æ¿å ¥ node.after(postcss.rule({ selectors, nodes })); } // ç®å°ã³ã¡ã³ãã¯åé¤ node.remove(); current.remove(); } }); console.log(root.toString());
ãã®ä½æ¥ãè¡ã£ãå¾ã®å·®åã®ä¸é¨ã®ã¹ã¯ãªã¼ã³ã·ã§ããã§ããå¤ãã®ã»ã¬ã¯ã¿ãããã«ã¼ã«ã§ãå®éã«å©ç¨ãã¦ãã2~3åã ã£ãããã¦ãå¤ãã®ã»ã¬ã¯ã¿ãåé¤ã§ãã¾ããã
ããã
Puppeteerã®ã«ãã¬ãã¸åéæ©è½ã使ã£ã¦ãCSSãåé¤ããäºä¾ãç´¹ä»ãã¾ãããä»åã¯ãã¼ã¸ã®è¡¨ç¤ºãã¿ã¼ã³ãå°ãªããããã¯ãã ã£ãã®ã§æ¥½ã§ãããããã¼ã¸æ°ã»è¡¨ç¤ºãã¿ã¼ã³ãå¤ãå ´åã¯ç¹å®ã®ãã¼ã¸ã«çµã£ã¦ã«ãã¬ãã¸ãåãã¨ãã¾ããããããªäºæããã¾ãã ã¾ããPuppeteerã®ã«ãã¬ãã¸åéæ©è½ã¯Google I/O '19ã®åç»ã観ã¦ç¥ã£ãã®ã§ãããããããã¼ã ã§Webããã³ãã¨ã³ãã®æ¢ç©¶æ´»åãè¡ãã¤ã¤ããããã¯ããã©ãã©ãæ¹åãã¦ããããã§ãã
PR
9æ26æ¥ï¼æ¨ï¼ã«ç¦å²¡ã§Cybozu Meetupãéå¬ãã¾ããããã³ãã¨ã³ãã¨ãã¹ãã¼ããã¼ã ã¯ãã¡ãããçç£æ§åä¸ãã¼ã ããKintoneãªã©ã®ãããã¯ããã¼ã ãç¦å²¡ã«éçµãã¾ããã¤ãã³ããã¼ã¸ã¯8æä¸ã«ãµã¤ãã¦ãºã®connpassã«ã¦å ¬éäºå®ã§ããç¦å²¡ã®ã¨ã³ã¸ãã¢ã®çããã¨ããããæè¡ã®è©±ãã§ããã®ã楽ãã¿ã«ãã¦ããã¾ãï¼
å¤æ´å±¥æ´
2020/04/07
以ä¸ã®ã³ã¼ãã«èª¤ãããã£ãã®ã§ä¿®æ£ãã¾ããã
// â¢ãã¼ãã®ã½ã¼ã¹ã³ã¼ãä¸ã®ç¯å²ãå«ãã«ãã¬ãã¸ãæ¢ã const covered = coverage.ranges.find( - range => !(startIndex => range.end || endIndex < range.start) + range => !(startIndex >= range.end || endIndex < range.start) );