[docs]
class WebpackEntryPointProvider:
"""
An extension that provides Webpack entry points.
"""
[docs]
@classmethod
def webpack_entry_point_directory_path(cls) -> Path:
"""
Get the path to the directory with the entry point assets.
The directory must include at least a ``package.json`` and ``main.ts``.
"""
raise NotImplementedError
[docs]
def webpack_entry_point_cache_keys(self) -> Sequence[str]:
"""
Get the keys that make a Webpack build for this provider unique.
Providers that can be cached regardless may ``return ()``.
"""
raise NotImplementedError
[docs]
class Webpack(Extension, CssProvider, Jinja2Provider, Generator):
"""
Integrate Betty with `Webpack <https://webpack.js.org/>`_.
"""
_npm_requirement = NpmRequirement()
_prebuilt_assets_requirement = PrebuiltAssetsRequirement()
_requirement = AnyRequirement(
_npm_requirement,
_prebuilt_assets_requirement,
)
[docs]
@override
@classmethod
def name(cls) -> str:
return "betty.extension.Webpack"
[docs]
@override
@classmethod
def enable_requirement(cls) -> Requirement:
return AllRequirements(super().enable_requirement(), cls._requirement)
[docs]
def build_requirement(self) -> Requirement:
"""
Get the requirement that must be satisfied for Webpack builds to be available.
"""
return self._npm_requirement
[docs]
@override
@classmethod
def assets_directory_path(cls) -> Path:
return Path(__file__).parent / "assets"
@override
@property
def public_css_paths(self) -> list[str]:
return [
self.app.static_url_generator.generate("css/vendor.css"),
]
[docs]
@override
def new_context_vars(self) -> dict[str, Any]:
return {
"webpack_js_entry_points": set(),
}
@override
@property
def filters(self) -> dict[str, Callable[..., Any]]:
return FILTERS
@property
def _project_entry_point_providers(
self,
) -> Sequence[WebpackEntryPointProvider & Extension]:
return [
extension
for extension in self._app.extensions.flatten()
if isinstance(extension, WebpackEntryPointProvider)
]
[docs]
@override
async def generate(self, job_context: GenerationContext) -> None:
build_directory_path = await self._generate_ensure_build_directory(
job_context=job_context,
)
await self._copy_build_directory(
build_directory_path, self._app.project.configuration.www_directory_path
)
[docs]
async def prebuild(self, job_context: Context) -> None:
"""
Prebuild the Webpack assets.
"""
async with TemporaryDirectory() as working_directory_path_str:
build_directory_path = await self._new_builder(
Path(working_directory_path_str),
job_context=job_context,
).build()
await self._copy_build_directory(
build_directory_path,
_prebuilt_webpack_build_directory_path(
self._project_entry_point_providers
),
)
def _new_builder(
self,
working_directory_path: Path,
*,
job_context: Context,
) -> build.Builder:
return build.Builder(
working_directory_path,
self._project_entry_point_providers,
self._app.project.configuration.debug,
self._app.renderer,
job_context=job_context,
localizer=self._app.localizer,
)
async def _copy_build_directory(
self,
build_directory_path: Path,
destination_directory_path: Path,
) -> None:
await to_thread(
copytree,
build_directory_path,
destination_directory_path,
dirs_exist_ok=True,
)
async def _generate_ensure_build_directory(
self,
*,
job_context: Context,
) -> Path:
builder = self._new_builder(
self._app.binary_file_cache.with_scope("webpack").path,
job_context=job_context,
)
try:
# (Re)build the assets if `npm` is available.
return await builder.build()
except NpmUnavailable:
pass
# Use prebuilt assets if they exist.
prebuilt_webpack_build_directory_path = _prebuilt_webpack_build_directory_path(
self._project_entry_point_providers
)
if prebuilt_webpack_build_directory_path.exists():
return prebuilt_webpack_build_directory_path
raise RequirementError(self._requirement)