Skip to content

Commit

Permalink
Merge pull request #27 from RinteRface/component-based-bundling
Browse files Browse the repository at this point in the history
Component based bundling
  • Loading branch information
DivadNojnarg committed Jan 24, 2024
2 parents 8f4f018 + fe75615 commit 4f37f3d
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 60 deletions.
8 changes: 7 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: charpente
Title: Seamlessly design robust 'shiny' extensions
Version: 0.6.0
Version: 0.7.0
Authors@R: c(
person(
given = "David",
Expand All @@ -15,6 +15,12 @@ Authors@R: c(
role = "ctb",
comment = "Functions from html2r"
),
person(
given = "Veerle",
family = "van Leemput",
role = "ctb",
email = "[email protected]"
),
person(family = "RinteRface", role = "cph"),
person(family = "ThinkR", role = "cph", comment = "Some utils from golem")
)
Expand Down
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# charpente 0.7.0

## New:
- Enhanced `esbuild` configuration to allow component based bundling. `build_js` now accepts an `entry_points` argument to specify the entry files processed by `esbuild`. The (minified) output files will correspond to the given entry files. The default is monolithic building with the `./srcjs/main.js` file. See `?build_js` for more details.

# charpente 0.6.0

## New:
Expand Down
113 changes: 70 additions & 43 deletions R/deps.R
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,11 @@ create_dependency <- function(name, tag = NULL, open = interactive(), options =
#'
#' @param name Package name.
#' @param version Package version.
#' @param entry_points Entry points to create dependency for.
#' @param open Whether to allow rstudioapi to open the newly created script. Default to TRUE.
#' @param mode Internal. Don't use.
#' @keywords Internal
create_custom_dependency <- function(name, version, open = interactive(), mode) {

stylesheet <- sprintf("%s%s.css", name, mode)
script <- sprintf("%s%s.js", name, mode)
create_custom_dependency <- function(name, version, entry_points, open = interactive(), mode) {

# need to overwrite path which was used before
path <- sprintf("R/%s-dependencies.R", name)
Expand All @@ -203,53 +201,82 @@ create_custom_dependency <- function(name, version, open = interactive(), mode)
write(..., file = path, append = TRUE)
}

# if we have multiple scripts/stylesheets
insert_multiple_lines <- function(what) {
lapply(seq_along(what), function (i) {
if (i == length(what)) {
write_there(sprintf(' "%s"', what[[i]]))
} else {
write_there(sprintf(' "%s",', what[[i]]))
if (length(entry_points) > 1) {
# remove everything before last / and remove .js
entry_point_names <- gsub(".*/|.js", "", entry_points)

# Check for special characters, digits, and replace - with _
has_special_or_digit <- grepl("[^a-zA-Z\\s]|\\d", entry_point_names)
has_hyphen <- grepl("-", entry_point_names)

if (any(has_special_or_digit)) {
format_names <- entry_point_names[has_special_or_digit]

ui_warn(
"Consider removing special characters or digits from the following entry point filenames:
{paste(format_names, collapse = ', ')}"
)

if (any(has_hyphen)) {
entry_point_names[has_hyphen] <- gsub("-", "_", entry_point_names[has_hyphen])

format_names <- entry_point_names[has_hyphen]

ui_done(
"Replaced - with _ in the following entry points:
{paste(format_names, collapse = ', ')}"
)
}
})
}

} else {
entry_point_names <- name
}

# roxygen export
write_there(sprintf("#' %s dependencies utils", name))
write_there("#'")
write_there(sprintf("#' @description This function attaches %s dependencies to the given tag", name))
write_there("#'")
write_there("#' @param tag Element to attach the dependencies.")
write_there("#'")
write_there("#' @importFrom utils packageVersion")
write_there("#' @importFrom htmltools tagList htmlDependency")
write_there("#' @export")
# attach function
write_there(sprintf("add_%s_deps <- function(tag) {", name))

# htmlDependency content
write_there(sprintf(" %s_deps <- htmlDependency(", name))
write_there(sprintf(' name = "%s",', name))
write_there(sprintf(' version = "%s",', version))
write_there(sprintf(' src = c(file = "%s-%s"),', name, version))
write_there(sprintf(' script = "dist/%s",', script))
write_there(sprintf(' stylesheet = "dist/%s",', stylesheet))
write_there(sprintf(' package = "%s",', name))
# end deps
write_there(" )")

# attach deps
write_there(sprintf(" tagList(tag, %s_deps)", name))
# end function
write_there("}")
write_there(" ")
# write in file for each entry point
lapply(entry_point_names, function(dep) {

stylesheet <- sprintf("%s%s.css", dep, mode)
script <- sprintf("%s%s.js", dep, mode)

# roxygen export
write_there(sprintf("#' %s dependencies utils", dep))
write_there("#'")
write_there(sprintf("#' @description This function attaches %s dependencies to the given tag", dep))
write_there("#'")
write_there("#' @param tag Element to attach the dependencies.")
write_there("#'")
write_there("#' @importFrom utils packageVersion")
write_there("#' @importFrom htmltools tagList htmlDependency")
write_there("#' @export")
# attach function
write_there(sprintf("add_%s_deps <- function(tag) {", dep))

# htmlDependency content
write_there(sprintf(" %s_deps <- htmlDependency(", dep))
write_there(sprintf(' name = "%s",', dep))
write_there(sprintf(' version = "%s",', version))
write_there(sprintf(' src = c(file = "%s-%s"),', name, version))
write_there(sprintf(' script = "dist/%s",', script))
write_there(sprintf(' stylesheet = "dist/%s",', stylesheet))
write_there(sprintf(' package = "%s",', name))
# end deps
write_there(" )")

# attach deps
write_there(sprintf(" tagList(tag, %s_deps)", dep))
# end function
write_there("}")
write_there(" ")
})

# path to dependency
if (!file.exists(sprintf("R/%s-dependencies.R", name))) {
ui_done("Dependency successfully created!")
ui_oops("Failed to create dependencies file!")
} else {
ui_done("Dependency successfully updated!")
ui_done("Dependencies successfully updated!")
}

if (open && rstudioapi::isAvailable()) rstudioapi::navigateToFile(path)
}

Expand Down
24 changes: 20 additions & 4 deletions R/jstools.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,37 @@
#' @param mode Production or development mode. Choose either "prod" or "dev".
#' "prod" bundles, aggregates and minifyies files. "dev" only bundles the code.
#' Modules follow the ES6 format (import/export).
#' @param entry_points Entry point(s) to use in esbuild configuration. In case of
#' a monolithic bundle, only one entrypoint is needed. This the default.
#' In case of component based bundles, a vector of entrypoints is needed.
#' The output files will match the entrypoints names.
#' @export
#' @importFrom utils tail packageVersion
build_js <- function(dir = "srcjs", mode = c("prod", "dev")) {
build_js <- function(dir = "srcjs", mode = c("prod", "dev"), entry_points = "main.js") {

mode <- match.arg(mode)
pkg_desc <- desc::description$new("./DESCRIPTION")$get(c("Package", "Version", "License"))
outputDir <- sprintf("inst/%s-%s/dist", pkg_desc[1], pkg_desc[2])

# make sure entrypoints look like ./dir/file.js
if (any(!grepl(sprintf("^\\./%s", dir), entry_points))) {
entry_points <- paste0("./", dir, "/", entry_points)
}

# check if entry_points exists
if (any(!file.exists(entry_points))) {
missing_entry_points <- entry_points[!file.exists(entry_points)]
ui_stop("The following entry points don't exist: {paste0(entry_points, collapse = ', ')}")
}

# run esbuild
run_esbuild(mode, outputDir)
run_esbuild(mode, outputDir, entry_points)

# create custom dependency
create_custom_dependency(
pkg_desc[1],
pkg_desc[2],
name = pkg_desc[1],
version = pkg_desc[2],
entry_points = entry_points,
open = FALSE,
mode = if (mode == "dev") "" else if (mode == "prod") ".min"
)
Expand Down
13 changes: 9 additions & 4 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ process_template <- function(template, ..., where = system.file("utils", package
)
},
version = pars$version,
entry_point = "main.js",
entry_points = paste0(shQuote(pars$entry_points), collapse = ", "),
entry_name = pars$entry_name,
license = pars$license,
.open = "<<",
.close = ">>"
Expand Down Expand Up @@ -170,7 +171,8 @@ set_esbuild <- function(light = FALSE) {
"package.json",
name = pkg_desc[1],
version = pkg_desc[2], # node does not support 0.1.0.9000
license = pkg_desc[3]
license = pkg_desc[3],
entry_points = "main.js"
)

npm::npm_install(
Expand Down Expand Up @@ -300,8 +302,9 @@ copy_charpente_utils <- function(pkg_name) {
#'
#' @inheritParams build_js
#' @param outputDir Output directory
#' @param entry_points Entry points to be used
#' @keywords internal
run_esbuild <- function(mode, outputDir) {
run_esbuild <- function(mode, outputDir, entry_points) {
# styles did not exist in previous {charpent} versions
if (!dir.exists("styles")) {
# Only add missing pieces ...
Expand All @@ -317,7 +320,9 @@ run_esbuild <- function(mode, outputDir) {
process_template(
sprintf("esbuild.%s.js", mode),
name = pkg_desc[[1]],
version = pkg_desc[[2]]
entry_name = if (length(entry_points) == 1) pkg_desc[[1]] else "[name]",
version = pkg_desc[[2]],
entry_points = entry_points
)

npm::npm_run(sprintf("run build-%s", mode))
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,34 @@ set_esbuild()
set_mocha()
```

## Monolithic and component based bundling

`{charpente}` offers two ways of bundling your JS code: monolithic and component based. The monolithic bundling is the default one and is the simplest to use. It will bundle all your JS and CSS code into single files. By default, the entry point is `/scrjs/main.js`, which will also be created when setting esbuild for the first time with `set_esbuid()`. In this case bundling is easy and can be achieved with:

```r
build_js()
```

Component based bundling might be convenient in situations where you want to create a bundle for each (standalone) component. Bundling per component will make sure that only the necessary assets are loaded. This is particularly useful when you want to create a package with multiple components, and not a complete template. To use component based bundling, you can specify multiple `entry_points` in `build_js()`.

For the below structure in the /srcjs folder:

```
srcjs
├── component1.js
└── component2.js
```

You can build as follows:

```r
build_js(entry_points = c("component1.js", "component2.js"))
```

CSS styles for each component can be loaded in the js files with `import` statements, e.g. `import "../styles/component1.scss";`.

You JS code will be bundled into `/inst/{package-name}-{version}/`. Dependencies for your HTML are automatically created in `R/{package-name}-dependencies.R`. There will be only one HTML dependency in case of monolithic building, and multiple in case of component based building.

## Acknowledgment
The author would like to warmly thank [Victor Perrier](https://twitter.com/_pvictorr?lang=fr),
[John Coene](https://twitter.com/jdatap), [Colin Fay](https://twitter.com/_ColinFay), [Alan Dipert](https://twitter.com/alandipert), [Kenton Russel](https://twitter.com/timelyportfolio) for providing many building block and inspiration to this package.
Expand Down
5 changes: 3 additions & 2 deletions inst/utils/esbuild.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import autoprefixer from 'autoprefixer';

esbuild
.build({
entryPoints: ["./srcjs/main.js"],
outfile: "inst/<<name>>-<<version>>/dist/<<name>>.js",
entryPoints: [<<entry_points>>],
outdir: "inst/<<name>>-<<version>>/dist",
entryNames: "<<entry_name>>",
bundle: true,
format: "esm",
minify: false, // dev
Expand Down
5 changes: 3 additions & 2 deletions inst/utils/esbuild.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import autoprefixer from 'autoprefixer';

esbuild
.build({
entryPoints: ["./srcjs/main.js"],
outfile: "inst/<<name>>-<<version>>/dist/<<name>>.min.js",
entryPoints: [<<entry_points>>],
outdir: "inst/<<name>>-<<version>>/dist",
entryNames: "<<entry_name>>.min",
bundle: true,
format: "esm",
minify: true, // prod
Expand Down
2 changes: 1 addition & 1 deletion inst/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "<<split_version>>",
"description": "",
"private": true,
"main": "<<entry_point>>",
"main": "<<entry_points>>",
"directories": {
"man": "man"
},
Expand Down
7 changes: 6 additions & 1 deletion man/build_js.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion man/create_custom_dependency.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion man/run_esbuild.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4f37f3d

Please sign in to comment.