add parcel watcher
parent
01278a9d4d
commit
2deda99fe7
|
@ -25,6 +25,7 @@ import {
|
|||
HoverRequest,
|
||||
DidChangeWatchedFilesNotification,
|
||||
FileChangeType,
|
||||
Disposable,
|
||||
} from 'vscode-languageserver/node'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import { URI } from 'vscode-uri'
|
||||
|
@ -73,6 +74,7 @@ import { debounce } from 'debounce'
|
|||
import { getModuleDependencies } from './util/getModuleDependencies'
|
||||
import assert from 'assert'
|
||||
// import postcssLoadConfig from 'postcss-load-config'
|
||||
import * as parcel from './watcher/index.js'
|
||||
|
||||
const CONFIG_FILE_GLOB = '{tailwind,tailwind.config}.{js,cjs}'
|
||||
const TRIGGER_CHARACTERS = [
|
||||
|
@ -151,6 +153,7 @@ function first<T>(...options: Array<() => T>): T {
|
|||
interface ProjectService {
|
||||
state: State
|
||||
tryInit: () => Promise<void>
|
||||
dispose: () => void
|
||||
onUpdateSettings: (settings: any) => void
|
||||
onHover(params: TextDocumentPositionParams): Promise<Hover>
|
||||
onCompletion(params: CompletionParams): Promise<CompletionList>
|
||||
|
@ -167,6 +170,7 @@ async function createProjectService(
|
|||
params: InitializeParams,
|
||||
documentService: DocumentService
|
||||
): Promise<ProjectService> {
|
||||
const disposables: Disposable[] = []
|
||||
const state: State = {
|
||||
enabled: false,
|
||||
editor: {
|
||||
|
@ -208,7 +212,13 @@ async function createProjectService(
|
|||
const documentSettingsCache: Map<string, Settings> = new Map()
|
||||
let registrations: Promise<BulkUnregistration>
|
||||
|
||||
let watcher: FSWatcher
|
||||
let chokidarWatcher: FSWatcher
|
||||
let ignore = [
|
||||
'**/.git/objects/**',
|
||||
'**/.git/subtree-cache/**',
|
||||
'**/node_modules/**',
|
||||
'**/.hg/store/**',
|
||||
]
|
||||
|
||||
function onFileEvents(changes: Array<{ file: string; type: FileChangeType }>): void {
|
||||
let needsInit = false
|
||||
|
@ -217,22 +227,30 @@ async function createProjectService(
|
|||
for (let change of changes) {
|
||||
let file = normalizePath(change.file)
|
||||
|
||||
for (let ignorePattern of ignore) {
|
||||
if (minimatch(file, ignorePattern)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let isConfigFile = minimatch(file, `**/${CONFIG_FILE_GLOB}`)
|
||||
let isPackageFile = minimatch(file, '**/package.json')
|
||||
let isDependency = state.dependencies && state.dependencies.includes(change.file)
|
||||
|
||||
if (!isConfigFile && !isPackageFile && !isDependency) continue
|
||||
|
||||
if (change.type === FileChangeType.Created) {
|
||||
needsInit = true
|
||||
break
|
||||
} else if (change.type === FileChangeType.Changed) {
|
||||
if (!state.enabled || minimatch(file, '**/package.json')) {
|
||||
if (!state.enabled || isPackageFile) {
|
||||
needsInit = true
|
||||
break
|
||||
} else {
|
||||
needsRebuild = true
|
||||
}
|
||||
} else if (change.type === FileChangeType.Deleted) {
|
||||
if (
|
||||
!state.enabled ||
|
||||
minimatch(file, '**/package.json') ||
|
||||
minimatch(file, `**/${CONFIG_FILE_GLOB}`)
|
||||
) {
|
||||
if (!state.enabled || isPackageFile || isConfigFile) {
|
||||
needsInit = true
|
||||
break
|
||||
} else {
|
||||
|
@ -261,34 +279,59 @@ async function createProjectService(
|
|||
connection.client.register(DidChangeWatchedFilesNotification.type, {
|
||||
watchers: [{ globPattern: `**/${CONFIG_FILE_GLOB}` }, { globPattern: '**/package.json' }],
|
||||
})
|
||||
} else {
|
||||
watcher = chokidar.watch(
|
||||
[
|
||||
normalizePath(`${folder}/**/${CONFIG_FILE_GLOB}`),
|
||||
normalizePath(`${folder}/**/package.json`),
|
||||
],
|
||||
} else if (parcel.getBinding()) {
|
||||
let typeMap = {
|
||||
create: FileChangeType.Created,
|
||||
update: FileChangeType.Changed,
|
||||
delete: FileChangeType.Deleted,
|
||||
}
|
||||
|
||||
let subscription = await parcel.subscribe(
|
||||
folder,
|
||||
(err, events) => {
|
||||
onFileEvents(events.map((event) => ({ file: event.path, type: typeMap[event.type] })))
|
||||
},
|
||||
{
|
||||
ignorePermissionErrors: true,
|
||||
ignoreInitial: true,
|
||||
ignored: ['**/node_modules/**'],
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 100,
|
||||
pollInterval: 20,
|
||||
},
|
||||
ignore: ignore.map((ignorePattern) =>
|
||||
path.resolve(folder, ignorePattern.replace(/^[*/]+/, '').replace(/[*/]+$/, ''))
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
watcher.on('ready', () => resolve())
|
||||
disposables.push({
|
||||
dispose() {
|
||||
subscription.unsubscribe()
|
||||
},
|
||||
})
|
||||
} else {
|
||||
chokidarWatcher = chokidar.watch([`**/${CONFIG_FILE_GLOB}`, '**/package.json'], {
|
||||
cwd: folder,
|
||||
ignorePermissionErrors: true,
|
||||
ignoreInitial: true,
|
||||
ignored: ignore,
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 100,
|
||||
pollInterval: 20,
|
||||
},
|
||||
})
|
||||
|
||||
watcher
|
||||
await new Promise<void>((resolve) => {
|
||||
chokidarWatcher.on('ready', () => resolve())
|
||||
})
|
||||
|
||||
chokidarWatcher
|
||||
.on('add', (file) => onFileEvents([{ file, type: FileChangeType.Created }]))
|
||||
.on('change', (file) => onFileEvents([{ file, type: FileChangeType.Changed }]))
|
||||
.on('unlink', (file) => onFileEvents([{ file, type: FileChangeType.Deleted }]))
|
||||
|
||||
disposables.push({
|
||||
dispose() {
|
||||
chokidarWatcher.close()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function registerCapabilities(watchFiles?: string[]): void {
|
||||
function registerCapabilities(watchFiles: string[] = []): void {
|
||||
if (supportsDynamicRegistration(connection, params)) {
|
||||
if (registrations) {
|
||||
registrations.then((r) => r.dispose())
|
||||
|
@ -310,7 +353,7 @@ async function createProjectService(
|
|||
resolveProvider: true,
|
||||
triggerCharacters: [...TRIGGER_CHARACTERS, state.separator],
|
||||
})
|
||||
if (watchFiles) {
|
||||
if (watchFiles.length > 0) {
|
||||
capabilities.add(DidChangeWatchedFilesNotification.type, {
|
||||
watchers: watchFiles.map((file) => ({ globPattern: file })),
|
||||
})
|
||||
|
@ -323,13 +366,13 @@ async function createProjectService(
|
|||
function resetState(): void {
|
||||
clearAllDiagnostics(state)
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key !== 'editor') {
|
||||
// Keep `dependencies` to ensure that they are still watched
|
||||
if (key !== 'editor' && key !== 'dependencies') {
|
||||
delete state[key]
|
||||
}
|
||||
})
|
||||
state.enabled = false
|
||||
registerCapabilities()
|
||||
// TODO reset watcher (remove config dependencies)
|
||||
registerCapabilities(state.dependencies)
|
||||
}
|
||||
|
||||
async function tryInit() {
|
||||
|
@ -813,10 +856,10 @@ async function createProjectService(
|
|||
}
|
||||
|
||||
if (state.dependencies) {
|
||||
watcher?.unwatch(state.dependencies)
|
||||
chokidarWatcher?.unwatch(state.dependencies)
|
||||
}
|
||||
state.dependencies = getModuleDependencies(state.configPath)
|
||||
watcher?.add(state.dependencies)
|
||||
chokidarWatcher?.add(state.dependencies)
|
||||
|
||||
state.configId = getConfigId(state.configPath, state.dependencies)
|
||||
|
||||
|
@ -837,6 +880,11 @@ async function createProjectService(
|
|||
return {
|
||||
state,
|
||||
tryInit,
|
||||
dispose() {
|
||||
for (let { dispose } of disposables) {
|
||||
dispose()
|
||||
}
|
||||
},
|
||||
onUpdateSettings(settings: any): void {
|
||||
documentSettingsCache.clear()
|
||||
if (state.enabled) {
|
||||
|
@ -1279,7 +1327,9 @@ class TW {
|
|||
}
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
for (let [, project] of this.projects) {
|
||||
project.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
const os = require('os')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const vars = (process.config && process.config.variables) || {}
|
||||
const arch = os.arch()
|
||||
const platform = os.platform()
|
||||
const abi = process.versions.modules
|
||||
const runtime = isElectron() ? 'electron' : 'node'
|
||||
const libc = process.env.LIBC || (isAlpine(platform) ? 'musl' : 'glibc')
|
||||
const armv = process.env.ARM_VERSION || (arch === 'arm64' ? '8' : vars.arm_version) || ''
|
||||
const uv = (process.versions.uv || '').split('.')[0]
|
||||
|
||||
const prebuilds = {
|
||||
'darwin-x64': {
|
||||
'node.napi.glibc.node': () => require('./prebuilds/darwin-x64.node.napi.glibc.node'),
|
||||
},
|
||||
'linux-x64': {
|
||||
'node.napi.glibc.node': () => require('./prebuilds/linux-x64.node.napi.glibc.node'),
|
||||
'node.napi.musl.node': () => require('./prebuilds/linux-x64.node.napi.musl.node'),
|
||||
},
|
||||
'win32-x64': {
|
||||
'node.napi.glibc.node': () => require('./prebuilds/win32-x64.node.napi.glibc.node'),
|
||||
},
|
||||
}
|
||||
|
||||
let getBinding = () => {
|
||||
let resolved = resolve()
|
||||
getBinding = () => resolved
|
||||
return resolved
|
||||
}
|
||||
|
||||
exports.getBinding = getBinding
|
||||
|
||||
exports.writeSnapshot = (dir, snapshot, opts) => {
|
||||
return getBinding().writeSnapshot(
|
||||
path.resolve(dir),
|
||||
path.resolve(snapshot),
|
||||
normalizeOptions(dir, opts)
|
||||
)
|
||||
}
|
||||
|
||||
exports.getEventsSince = (dir, snapshot, opts) => {
|
||||
return getBinding().getEventsSince(
|
||||
path.resolve(dir),
|
||||
path.resolve(snapshot),
|
||||
normalizeOptions(dir, opts)
|
||||
)
|
||||
}
|
||||
|
||||
exports.subscribe = async (dir, fn, opts) => {
|
||||
dir = path.resolve(dir)
|
||||
opts = normalizeOptions(dir, opts)
|
||||
await getBinding().subscribe(dir, fn, opts)
|
||||
|
||||
return {
|
||||
unsubscribe() {
|
||||
return getBinding().unsubscribe(dir, fn, opts)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
exports.unsubscribe = (dir, fn, opts) => {
|
||||
return getBinding().unsubscribe(path.resolve(dir), fn, normalizeOptions(dir, opts))
|
||||
}
|
||||
|
||||
function resolve() {
|
||||
// Find most specific flavor first
|
||||
var list = prebuilds[platform + '-' + arch]
|
||||
var builds = Object.keys(list)
|
||||
var parsed = builds.map(parseTags)
|
||||
var candidates = parsed.filter(matchTags(runtime, abi))
|
||||
var winner = candidates.sort(compareTags(runtime))[0]
|
||||
if (winner) return list[winner.file]()
|
||||
}
|
||||
|
||||
function parseTags(file) {
|
||||
var arr = file.split('.')
|
||||
var extension = arr.pop()
|
||||
var tags = { file: file, specificity: 0 }
|
||||
|
||||
if (extension !== 'node') return
|
||||
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var tag = arr[i]
|
||||
|
||||
if (tag === 'node' || tag === 'electron' || tag === 'node-webkit') {
|
||||
tags.runtime = tag
|
||||
} else if (tag === 'napi') {
|
||||
tags.napi = true
|
||||
} else if (tag.slice(0, 3) === 'abi') {
|
||||
tags.abi = tag.slice(3)
|
||||
} else if (tag.slice(0, 2) === 'uv') {
|
||||
tags.uv = tag.slice(2)
|
||||
} else if (tag.slice(0, 4) === 'armv') {
|
||||
tags.armv = tag.slice(4)
|
||||
} else if (tag === 'glibc' || tag === 'musl') {
|
||||
tags.libc = tag
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
tags.specificity++
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
function matchTags(runtime, abi) {
|
||||
return function (tags) {
|
||||
if (tags == null) return false
|
||||
if (tags.runtime !== runtime && !runtimeAgnostic(tags)) return false
|
||||
if (tags.abi !== abi && !tags.napi) return false
|
||||
if (tags.uv && tags.uv !== uv) return false
|
||||
if (tags.armv && tags.armv !== armv) return false
|
||||
if (tags.libc && tags.libc !== libc) return false
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function runtimeAgnostic(tags) {
|
||||
return tags.runtime === 'node' && tags.napi
|
||||
}
|
||||
|
||||
function compareTags(runtime) {
|
||||
// Precedence: non-agnostic runtime, abi over napi, then by specificity.
|
||||
return function (a, b) {
|
||||
if (a.runtime !== b.runtime) {
|
||||
return a.runtime === runtime ? -1 : 1
|
||||
} else if (a.abi !== b.abi) {
|
||||
return a.abi ? -1 : 1
|
||||
} else if (a.specificity !== b.specificity) {
|
||||
return a.specificity > b.specificity ? -1 : 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOptions(dir, opts = {}) {
|
||||
if (Array.isArray(opts.ignore)) {
|
||||
opts = Object.assign({}, opts, {
|
||||
ignore: opts.ignore.map((ignore) => path.resolve(dir, ignore)),
|
||||
})
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
function isElectron() {
|
||||
if (process.versions && process.versions.electron) return true
|
||||
if (process.env.ELECTRON_RUN_AS_NODE) return true
|
||||
return typeof window !== 'undefined' && window.process && window.process.type === 'renderer'
|
||||
}
|
||||
|
||||
function isAlpine(platform) {
|
||||
return platform === 'linux' && fs.existsSync('/etc/alpine-release')
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017-present Devon Govett
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Mathias Buus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -8,3 +8,4 @@ tests/**
|
|||
**/*.map
|
||||
.gitignore
|
||||
**/tsconfig.json
|
||||
**/*.node
|
||||
|
|
Loading…
Reference in New Issue