From ad1dce3c74a956ba6a977dcdc96d8f19a5cc1884 Mon Sep 17 00:00:00 2001 From: Ilya Medvedev Date: Thu, 1 Apr 2021 09:32:22 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20watch=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Closes: #9 --- README.md | 25 +++++++++++++++++++++++- __tests__/less-utils.test.ts | 18 ++++++++++++++++++ example/build.ts | 14 ++++++++++++++ package.json | 7 ++++--- src/index.ts | 15 ++++++++------- src/less-utils.ts | 37 ++++++++++++++++++++++++++++++++++++ yarn.lock | 8 ++++---- 7 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 __tests__/less-utils.test.ts create mode 100644 src/less-utils.ts diff --git a/README.md b/README.md index 95e4e09..04d9a0d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-f8bc45.svg)](https://github.com/prettier/prettier) [![npm version](https://badge.fury.io/js/esbuild-plugin-less.svg)](https://www.npmjs.com/package/esbuild-plugin-less) -[![David](https://img.shields.io/david/dev/iam-medvedev/esbuild-plugin-less)](https://david-dm.org/iam-medvedev/esbuild-plugin-less) +[![David](https://status.david-dm.org/gh/iam-medvedev/esbuild-plugin-less.svg?type=dev)](https://david-dm.org/iam-medvedev/esbuild-plugin-less) +[![David](https://status.david-dm.org/gh/iam-medvedev/esbuild-plugin-less.svg?type=peer)](https://david-dm.org/iam-medvedev/esbuild-plugin-less) [![Codecov](https://img.shields.io/codecov/c/github/iam-medvedev/esbuild-plugin-less)](https://codecov.io/gh/iam-medvedev/esbuild-plugin-less) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fiam-medvedev%2Fesbuild-plugin-less.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fiam-medvedev%2Fesbuild-plugin-less?ref=badge_shield) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) @@ -19,6 +20,8 @@ yarn add esbuild-plugin-less -D ## Usage +### Simple example + You can see the example [here](./example). ```ts @@ -36,6 +39,26 @@ build({ }); ``` +### Watch mode + +More information about watch mode [here](https://esbuild.github.io/api/#watch). + +```ts +import { build } from 'esbuild'; +import { lessLoader } from 'esbuild-plugin-less'; + +build({ + watch: true, // enable watch mode + entryPoints: [path.resolve(__dirname, 'index.ts')], + bundle: true, + outdir: path.resolve(__dirname, 'output'), + plugins: [lessLoader()], + loader: { + '.ts': 'ts', + }, +}); +``` + ## Options `lessLoader` accepts all valid options from less.js. You can find a complete list of options [here](http://lesscss.org/usage/#less-options). diff --git a/__tests__/less-utils.test.ts b/__tests__/less-utils.test.ts new file mode 100644 index 0000000..0dbe121 --- /dev/null +++ b/__tests__/less-utils.test.ts @@ -0,0 +1,18 @@ +import path from 'path'; +import { getLessImports } from '../src/less-utils'; + +describe('less-utils', () => { + it('get imports paths', () => { + const filePath = path.resolve(__dirname, '../example/styles/style.less'); + const imports = getLessImports(filePath); + + expect(imports).toEqual( + expect.arrayContaining([ + expect.stringContaining('styles/style-2.less'), + expect.stringContaining('styles/inner/style-3.less'), + expect.stringContaining('styles/inner/style-4.css'), + expect.stringContaining('styles/inner/style-5.css'), + ]), + ); + }); +}); diff --git a/example/build.ts b/example/build.ts index b69abbf..8852d5e 100644 --- a/example/build.ts +++ b/example/build.ts @@ -2,7 +2,21 @@ import * as path from 'path'; import { build } from 'esbuild'; import { lessLoader } from '../src/index'; +// isProduction flag for watch mode +const isProduction = process.env.NODE_ENV === 'production'; + build({ + watch: isProduction + ? false + : { + onRebuild(error) { + if (error) { + console.error('Build failed:', error); + } else { + console.error('Build succeeded'); + } + }, + }, entryPoints: [path.resolve(__dirname, 'index.ts')], bundle: true, outdir: path.resolve(__dirname, 'output'), diff --git a/package.json b/package.json index 6b0ccca..c429c11 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ ], "scripts": { "build": "NODE_ENV=production ts-node ./scripts/build.ts", - "build:example": "ts-node ./example/build.ts", + "dev:example": "ts-node ./example/build.ts", + "build:example": "NODE_ENV=production ts-node ./example/build.ts", "commit": "yarn git-cz", "prepublish": "yarn test && yarn types && yarn build", "types": "NODE_ENV=production tsc --emitDeclarationOnly --declaration --outDir build", @@ -40,7 +41,7 @@ "@types/jest": "^26.0.20", "@types/node": "^14.14.22", "cz-conventional-changelog": "^3.3.0", - "esbuild": "^0.8.54", + "esbuild": "^0.11.2", "git-cz": "^4.7.6", "husky": "^5.1.3", "jest": "^26.6.3", @@ -52,7 +53,7 @@ "typescript": "^4.1.3" }, "peerDependencies": { - "esbuild": "^0.8.x" + "esbuild": "^0.11.x" }, "dependencies": { "@types/less": "^3.0.2", diff --git a/src/index.ts b/src/index.ts index 459edfe..88a287b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,8 @@ -import * as path from 'path'; +import path from 'path'; import { promises as fs } from 'fs'; import { Plugin } from 'esbuild'; import less from 'less'; - -const namespace = 'less'; +import { getLessImports } from './less-utils'; /** Less-loader for esbuild */ export function lessLoader(options: Less.Options = {}): Plugin { @@ -11,15 +10,17 @@ export function lessLoader(options: Less.Options = {}): Plugin { name: 'less-loader', setup: (build) => { // Resolve *.less files with namespace - build.onResolve({ filter: /\.less$/ }, (args) => { + build.onResolve({ filter: /\.less$/, namespace: 'file' }, (args) => { + const filePath = path.resolve(process.cwd(), path.relative(process.cwd(), args.resolveDir), args.path); + return { - path: path.resolve(process.cwd(), path.relative(process.cwd(), args.resolveDir), args.path), - namespace, + path: filePath, + watchFiles: !!build.initialOptions.watch ? [filePath, ...getLessImports(filePath)] : undefined, }; }); // Build .less files - build.onLoad({ filter: /.*/, namespace }, async (args) => { + build.onLoad({ filter: /\.less$/, namespace: 'file' }, async (args) => { const content = await fs.readFile(args.path, 'utf-8'); const dir = path.dirname(args.path); const filename = path.basename(args.path); diff --git a/src/less-utils.ts b/src/less-utils.ts new file mode 100644 index 0000000..d7ffa5e --- /dev/null +++ b/src/less-utils.ts @@ -0,0 +1,37 @@ +import fs from 'fs'; +import path from 'path'; + +const importRegex = /@import(?:\s+\((.*)\))?\s+['"](.*)['"]/; +const globalImportRegex = /@import(?:\s+\((.*)\))?\s+['"](.*)['"]/g; +const importCommentRegex = /(?:\/\*(?:[\s\S]*?)\*\/)|(\/\/(?:.*)$)/gm; + +const extWhitelist = ['.css', '.less']; + +/** Recursively get .less/.css imports from file */ +export const getLessImports = (filePath: string): string[] => { + try { + const dir = path.dirname(filePath); + const content = fs.readFileSync(filePath).toString('utf8'); + + const cleanContent = content.replace(importCommentRegex, ''); + const match = cleanContent.match(globalImportRegex) || []; + + const fileImports = match + .map((el) => { + const match = el.match(importRegex); + return match[2]; + }) + .filter((el) => !!el) + .map((el) => path.resolve(dir, el)); + + const recursiveImports = fileImports.reduce((result, el) => { + return [...result, ...getLessImports(el)]; + }, fileImports); + + const result = recursiveImports.filter((el) => extWhitelist.includes(path.extname(el).toLowerCase())); + + return result; + } catch (e) { + return []; + } +}; diff --git a/yarn.lock b/yarn.lock index f9c9902..2309b62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2359,10 +2359,10 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -esbuild@^0.8.54: - version "0.8.54" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.54.tgz#2f32ff80e95c69a0f25b799d76a27c05e2857cdf" - integrity sha512-DJH38OiTgXJxFb/EhHrCrY8eGmtdkTtWymHpN9IYN9AF+4jykT0dQArr7wzFejpVbaB0TMIq2+vfNRWr3LXpvw== +esbuild@^0.11.2: + version "0.11.2" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.11.2.tgz#3b995e107f2054d9090402b98a3b79ceffd05eb6" + integrity sha512-8d5FCQrR+juXC2u9zjTQ3+IYiuFuaWyKYwmApFJLquTrYNbk36H/+MkRQeTuOJg7IjUchRX2Ulwo1zRYXZ1pUg== escalade@^3.1.1: version "3.1.1"