Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions site/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ builders:
auto_apply: root_package
build_to: source

contentAssetsBuilder:
import: 'package:dart_dev_site/builders.dart'
builder_factories: [ 'contentAssetsBuilder' ]
build_extensions:
'src/content/{{}}.png': ['web/images/content/{{}}.png']
'src/content/{{}}.jpg': ['web/images/content/{{}}.jpg']
'src/content/{{}}.jpeg': ['web/images/content/{{}}.jpeg']
'src/content/{{}}.gif': ['web/images/content/{{}}.gif']
'src/content/{{}}.svg': ['web/images/content/{{}}.svg']
auto_apply: root_package
build_to: source

targets:
$default:
builders:
Expand All @@ -28,3 +40,10 @@ targets:
dart_dev_site:stylesHashBuilder:
dev_options:
fixed_hash: true
dart_dev_site:contentAssetsBuilder:
options: {}
sources:
- lib/**
- web/**
- src/content/**
- pubspec.yaml
174 changes: 174 additions & 0 deletions site/filesystem_loader_temp.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import 'dart:async';

import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:jaspr/server.dart';
import 'package:jaspr_router/jaspr_router.dart';
import 'package:path/path.dart' as p;
import 'package:watcher/watcher.dart';

import '../page.dart';
import '../utils.dart';
import 'route_loader.dart';

/// A loader that loads routes from the filesystem.
///
/// Routes are constructed based on the recursive folder structure under the root [directory].
/// Index files (index.*) are treated as the page for the containing folder.
/// Files and folders starting with an underscore (_) are ignored.
class FilesystemLoader extends RouteLoaderBase {
FilesystemLoader(
this.directory, {
this.keepSuffixPattern,
super.debugPrint,
@visibleForTesting this.fileSystem = const LocalFileSystem(),
@visibleForTesting DirectoryWatcherFactory? watcherFactory,
}) : watcherFactory = watcherFactory ?? _defaultWatcherFactory;

/// The directory to load pages from.
final String directory;

/// A pattern to keep the file suffix for all matching pages.
final Pattern? keepSuffixPattern;

@visibleForTesting
final FileSystem fileSystem;
@visibleForTesting
final DirectoryWatcherFactory watcherFactory;

static DirectoryWatcher _defaultWatcherFactory(String path) => DirectoryWatcher(path);

final Map<String, Set<PageSource>> dependentSources = {};

StreamSubscription<WatchEvent>? _watcherSub;

@override
Future<List<RouteBase>> loadRoutes(ConfigResolver resolver, bool eager) async {
if (kDebugMode) {
_watcherSub ??= watcherFactory(directory).events.listen((event) {
// It looks like event.path is relative on most platforms, but an
// absolute path on Linux. Turn this into the expected relative path.
final path = p.normalize(p.relative(event.path));
if (event.type == ChangeType.MODIFY) {
invalidateFile(path);
} else if (event.type == ChangeType.REMOVE) {
removeFile(path);
} else if (event.type == ChangeType.ADD) {
addFile(path);
}
Comment on lines +48 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The path handling for file watcher events appears to have issues with relative versus absolute paths. The removeFile and invalidateFile methods compare the incoming path with source.file.path, which is an absolute path. However, the path generated from a watch event is derived from p.relative(event.path), which likely produces a path relative to the current working directory, not an absolute one. This mismatch will cause hot-reloading of file removals and modifications to fail.

Furthermore, addFile also receives this relative path and attempts to compute a sub-path from it using directory, which can also fail if the paths are not consistently handled.

The entire path resolution logic for watcher events needs to be revisited to ensure that paths are consistently resolved to absolute paths before being used in these methods.

});
}
return super.loadRoutes(resolver, eager);
}

@override
void onReassemble() {
_watcherSub?.cancel();
_watcherSub = null;
}

@override
Future<String> readPartial(String path, Page page) {
return _getPartial(path, page).readAsString();
}

@override
String readPartialSync(String path, Page page) {
return _getPartial(path, page).readAsStringSync();
}

File _getPartial(String path, Page page) {
final pageSource = getSourceForPage(page);
if (pageSource != null) {
(dependentSources[path] ??= {}).add(pageSource);
}
return fileSystem.file(path);
}

@override
Future<List<PageSource>> loadPageSources() async {
final root = fileSystem.directory(directory);
if (!await root.exists()) {
return [];
}

List<PageSource> loadFiles(Directory dir) {
final List<PageSource> entities = [];
for (final entry in dir.listSync()) {
final path = entry.path.substring(root.path.length + 1);
if (entry is File) {
entities.add(
FilePageSource(
path,
entry,
this,
keepSuffix: keepSuffixPattern?.matchAsPrefix(entry.path) != null,
context: fileSystem.path,
),
);
} else if (entry is Directory) {
entities.addAll(loadFiles(entry));
}
}
return entities;
}

return loadFiles(root);
}

void addFile(String path) {
addSource(
FilePageSource(
path.substring(directory.length + 1),
fileSystem.file(path),
this,
keepSuffix: keepSuffixPattern?.matchAsPrefix(path) != null,
context: fileSystem.path,
),
);
}

void removeFile(String path) {
final source = sources.whereType<FilePageSource>().where((source) => source.file.path == path).firstOrNull;
if (source != null) {
removeSource(source);
}
}

void invalidateFile(String path, {bool rebuild = true}) {
final source = sources.whereType<FilePageSource>().where((source) => source.file.path == path).firstOrNull;
if (source != null) {
invalidateSource(source, rebuild: rebuild);
}
}

@override
void invalidateSource(PageSource source, {bool rebuild = true}) {
super.invalidateSource(source, rebuild: rebuild);
final fullPath = fileSystem.path.join(directory, source.path);
final dependencies = {...?dependentSources[fullPath]};
dependentSources[fullPath]?.clear();
for (final dependent in dependencies) {
invalidateSource(dependent, rebuild: rebuild);
}
}

@override
void invalidateAll() {
super.invalidateAll();
dependentSources.clear();
}
}

class FilePageSource extends PageSource {
FilePageSource(super.path, this.file, super.loader, {super.keepSuffix, super.context});

final File file;

@override
Future<Page> buildPage() async {
final content = await file.readAsString();

return Page(path: path, url: url, content: content, config: config, loader: loader);
}
}
1 change: 1 addition & 0 deletions site/lib/_sass/_site.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@use 'components/banner';
@use 'components/breadcrumbs';
@use 'components/buttons';
@use 'components/blog';
@use 'components/card';
@use 'components/code';
@use 'components/content';
Expand Down
Loading
Loading