diff --git a/DESCRIPTION b/DESCRIPTION index 91df10c..546d6c4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: charpente Title: Seamlessly design robust 'shiny' extensions -Version: 0.1.0.9000 +Version: 0.5.0.9000 Authors@R: c( person( given = "David", @@ -28,7 +28,6 @@ Description: 'charpente' eases the creation of 'shiny' extensions like 'shinydas more recently 'golem', that is make ('shiny') developer's life much easier. License: MIT + file LICENSE Encoding: UTF-8 -LazyData: true Roxygen: list(markdown = TRUE) RoxygenNote: 7.2.1 Imports: @@ -52,7 +51,6 @@ Imports: desc, gh Remotes: - ThinkR-open/golem@dev, JohnCoene/npm URL: https://github.com/RinteRface/charpente BugReports: https://github.com/RinteRface/charpente/issues diff --git a/NAMESPACE b/NAMESPACE index 5c951bb..7f777be 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,7 +4,6 @@ export("%>%") export(build_js) export(charpente_options) export(create_charpente) -export(create_css) export(create_custom_handler) export(create_dependency) export(create_input_binding) @@ -12,6 +11,7 @@ export(create_js) export(create_manifest) export(create_output_binding) export(create_pwa_dependency) +export(create_scss) export(get_dependency_assets) export(get_dependency_versions) export(get_installed_dependency) diff --git a/NEWS.md b/NEWS.md index 77ca113..0b6d499 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,22 @@ -# charpente 0.0.0.9000 +# charpent 0.5.0.9000 + +## Breaking change: +- Include Sass handling. SCSS files are sorted in the `/styles`folder and esbuild +has new Sass modules to treat them and generate CSS. +`package.json` now calls `node esbuild.dev.js` or `node esbuild.prod.js`, +depending on the selected mode, that is production or development. +`esbuild.**.js` is a new script which will be processed at run time in your project, before being called by `node` in `package.json`. If you come from an older +`{charpente}` version, `build_js()` will first try to create a `/styles` folder (which you normally don't have) and install missing Sass dependencies next to esbuild (`esbuild-sass-plugin`, `postcss`, `autoprefixer`). +- `get_dependency_assets()` leverages the new jsdlivr algorithm to infer the best entry point scripts for JS and CSS files, when downloading dependencies within `create_dependency()`. +This likely will change your vendors dependencies scripts names but should not change the features. +- `charpente_options()` will likely be deprecated because of the previous point. It still +contains the local option to either point to external CDN or copy external vendore files +into the local project. +- `create_css()` has been replaced by `create_scss()` (you can still use `golem::create_css`). +There is one main SCSS file created at project setup. Other SCSS files are referenced into this main `styles/main.scss` using `@import path`, which will allow you to have modular +Sass code. +- Remove `entry_point` param from `build_js()` as it was not used anyway... + +# charpente 0.1.0 * Added a `NEWS.md` file to track changes to the package. diff --git a/R/charpente.R b/R/charpente.R index ebfcd1e..7ae3096 100644 --- a/R/charpente.R +++ b/R/charpente.R @@ -68,7 +68,17 @@ create_charpente <- function(path, remote = NULL, private = FALSE, license) { # Add mocha for tests set_mocha() # Ignore files/folders: srcjs, node_modules, ... - use_build_ignore(c("srcjs", "node_modules", "package.json", "package-lock.json")) + use_build_ignore( + c( + "srcjs", + "node_modules", + "package.json", + "package-lock.json", + "styles", + "esbuild.dev.json", + "esbuild.prod.json" + ) + ) use_git_ignore("node_modules") # version control diff --git a/R/create_files.R b/R/create_files.R index 1303e9a..1f9fb35 100644 --- a/R/create_files.R +++ b/R/create_files.R @@ -5,7 +5,7 @@ #' @export #' @rdname create_file create_input_binding <- function(name, pkg = ".", dir = "srcjs", open = TRUE, - dir_create = TRUE, initialize = FALSE, dev = FALSE, + initialize = FALSE, dev = FALSE, events = list(name = "click", rate_policy = FALSE), add_reference = TRUE) { golem::add_js_input_binding( @@ -30,7 +30,7 @@ create_input_binding <- function(name, pkg = ".", dir = "srcjs", open = TRUE, #' @export #' @rdname create_file create_output_binding <- function(name, pkg = ".", dir = "srcjs", open = TRUE, - dir_create = TRUE, add_reference = TRUE) { + add_reference = TRUE) { golem::add_js_output_binding( name, pkg, @@ -56,7 +56,6 @@ create_custom_handler <- function( pkg = ".", dir = "srcjs", open = TRUE, - dir_create = TRUE, add_reference = TRUE ) { @@ -66,7 +65,7 @@ create_custom_handler <- function( pkg, dir, open, - dir_create, + dir_create = FALSE, template = charpente::js_handler_template( path = sprintf(paste0(dir, "/%s.js"), name), name = name @@ -104,14 +103,15 @@ create_custom_handler <- function( #' @param add_reference Whether to add an import statement in main.js. Defaults to TRUE. #' @export #' @rdname create_file -create_js <- function(name, pkg = ".", dir = "srcjs", open = TRUE, - dir_create = TRUE, with_doc_ready = TRUE, template = golem::js_template, +create_js <- function(name, dir = "srcjs", open = TRUE, + with_doc_ready = FALSE, + template = golem::js_template, ..., add_reference = TRUE) { # Create JS file golem::add_js_file( name, - pkg, - dir , + pkg = ".", + dir, open, dir_create = FALSE, with_doc_ready, @@ -123,13 +123,27 @@ create_js <- function(name, pkg = ".", dir = "srcjs", open = TRUE, if (add_reference) reference_script(name) } -#' Create a css file +#' Create a scss file #' -#' @inheritParams golem::add_css_file +#' @inheritParams golem::add_sass_file #' @export #' @rdname create_file -create_css <- partial( - golem::add_css_file, - pkg = ".", - dir = "inst" -) +create_scss <- function(name, dir = "styles", open = TRUE, + template = golem::sass_template, + ..., add_reference = TRUE) { + # Create JS file + golem::add_sass_file( + name, + pkg = ".", + dir , + open, + dir_create = FALSE, + template, + ... + ) + + file.rename(sprintf("%s/%s.sass", dir, name), sprintf("%s/%s.scss", dir, name)) + + # Import into main.scss + if (add_reference) reference_style(name) +} diff --git a/R/deps.R b/R/deps.R index f2a5b88..3fea812 100644 --- a/R/deps.R +++ b/R/deps.R @@ -191,8 +191,8 @@ create_dependency <- function(name, tag = NULL, open = interactive(), options = #' @keywords Internal create_custom_dependency <- function(name, version, open = interactive(), mode) { - stylesheet <- NULL # remove when Sass workflow is supported! - script <- sprintf("js/%s%s.js", name, mode) + stylesheet <- sprintf("%s%s.css", name, mode) + script <- sprintf("%s%s.js", name, mode) # need to overwrite path which was used before path <- sprintf("R/%s-dependencies.R", name) @@ -232,11 +232,8 @@ create_custom_dependency <- function(name, version, open = interactive(), mode) 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 = "%s",', script)) - if (!is.null(stylesheet)) { - stylesheet <- sprintf("css/%s%s.css", name, mode) - write_there(sprintf(' stylesheet = "%s",', stylesheet)) - } + write_there(sprintf(' script = "dist/%s",', script)) + write_there(sprintf(' stylesheet = "dist/%s",', stylesheet)) write_there(sprintf(' package = "%s",', name)) # end deps write_there(" )") diff --git a/R/jstools.R b/R/jstools.R index ed0a0a1..69794bc 100644 --- a/R/jstools.R +++ b/R/jstools.R @@ -8,10 +8,9 @@ #' @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_point Required internally to setup the esbuild config. #' @export #' @importFrom utils tail packageVersion -build_js <- function(dir = "srcjs", mode = c("prod", "dev"), entry_point = "main.js") { +build_js <- function(dir = "srcjs", mode = c("prod", "dev")) { mode <- match.arg(mode) pkg_desc <- desc::description$new("./DESCRIPTION")$get(c("Package", "Version", "License")) diff --git a/R/utils.R b/R/utils.R index 8871412..e2f25a7 100644 --- a/R/utils.R +++ b/R/utils.R @@ -94,7 +94,12 @@ process_template <- function(template, ..., where = system.file("utils", package temp <- glue::glue( readr::read_file(get_template(template, where)), name = pars$name, - split_version = paste(head(strsplit(pars$version, ".", fixed = TRUE)[[1]], -1), collapse = "."), + split_version = if (!is.null(version)) { + paste( + head(strsplit(pars$version, ".", fixed = TRUE)[[1]], -1), + collapse = "." + ) + }, version = pars$version, entry_point = "main.js", license = pars$license, @@ -103,7 +108,7 @@ process_template <- function(template, ..., where = system.file("utils", package ) write_there <- function(...) { - write(..., file = "./package.json", append = FALSE) + write(..., file = template, append = FALSE) } write_there(temp) } @@ -125,16 +130,34 @@ reference_script <- function(name) { ui_done("Script successfuly added to JS entry point!") } - +#' Insert import into main SCSS file +#' +#' Useful to reference scss files into the main one. +#' +#' @param path Could be file.name or folder/file.name +#' +#' @keywords internal +reference_style <- function(path) { + write( + sprintf("@import \"%s\";", path), + file = "./styles/main.scss", + append = TRUE + ) + ui_done("Style file successfuly added to Sass entry point!") +} #' Setup esbuild #' #' Installs esbuild for the local project #' +#' @param light Used to only install Sass plugins. This is +#' to workaround a breaking change in charpente where styles does +#' not exist in old versions. +#' #' @return Installs esbuild in node_modules (dev scope), creates srcjs + srcjs/main.js #' @keywords internal -set_esbuild <- function() { +set_esbuild <- function(light = FALSE) { pkg_desc <- desc::description$ new("./DESCRIPTION")$ @@ -148,9 +171,25 @@ set_esbuild <- function() { license = pkg_desc[3] ) - npm::npm_install("esbuild", scope = "dev") - dir.create("srcjs") - file.create("./srcjs/main.js") + npm::npm_install( + c( + # Don't re-install esbuild. This will install + # only missing Sass dependencies. + if (!light) "esbuild", + "esbuild-sass-plugin", + "postcss", + "autoprefixer" + ), + scope = "dev" + ) + # If light, we don't want to recreate srcjs folder + # which has always been in charpente since the first release. + if (!light) { + dir.create("srcjs") + write("import \"../styles/main.scss\";", "./srcjs/main.js") + } + dir.create("styles") + file.create("styles/main.scss") } @@ -227,6 +266,24 @@ copy_charpente_utils <- function(pkg_name) { #' @param outputDir Output directory #' @keywords internal run_esbuild <- function(mode, outputDir) { + # styles did not exist in previous {charpent} versions + if (!dir.exists("styles")) { + # Only add missing pieces ... + set_esbuild(light = TRUE) + } + + # Get pkg info + pkg_desc <- desc::description$ + new("./DESCRIPTION")$ + get(c("Package", "Version")) + + # Recreate esbuild.prod/dev.js each time + process_template( + sprintf("esbuild.%s.js", mode), + name = pkg_desc[[1]], + version = pkg_desc[[2]] + ) + npm::npm_run(sprintf("run build-%s", mode)) ui_warn(sprintf("%s folder created ...", outputDir)) ui_done("JavaScript successfully processed!") diff --git a/README.md b/README.md index 621889c..18687c6 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ The goal of `{charpente}` is to significantly reduce the complexity of creating - `{charpente}` automatically import dependencies from [jsdelivr](https://www.jsdelivr.com/), so that you don't have to do it by hand! - `{charpente}` eases the conversion from HTML to R. - `{charpente}` offers multiple R and JS boilerplate for `{shiny}` input bindings, `{shiny}` message handlers, ... - - `{charpente}` enable seamless JavaScript code management (powered by [esbuild](https://esbuild.github.io/)): concat, compress, mangle, bundle, minify, ... + - `{charpente}` enables seamless JavaScript code management (powered by [esbuild](https://esbuild.github.io/)): concat, compress, mangle, bundle, minify, ... for JS and + Sass code. ## Installation @@ -59,7 +60,7 @@ create_input_binding("myinput") # Create output binding create_output_binding("myoutput") -# Compress JS for production +# Compress JS and CSS (Sass) for production build_js() devtools::load_all() ``` diff --git a/inst/utils/esbuild.dev.js b/inst/utils/esbuild.dev.js new file mode 100644 index 0000000..cf38a8f --- /dev/null +++ b/inst/utils/esbuild.dev.js @@ -0,0 +1,23 @@ +import esbuild from "esbuild"; +import {sassPlugin} from 'esbuild-sass-plugin'; +import postcss from 'postcss'; +import autoprefixer from 'autoprefixer'; + +esbuild + .build({ + entryPoints: ["./srcjs/main.js"], + outfile: "inst/<>-<>/dist/<>.js", + bundle: true, + format: "esm", + minify: false, // dev + plugins: [ + sassPlugin({ + async transform(source) { + const { css } = await postcss([autoprefixer]).process(source); + return css; + }, + }), + ] + }) + .then(() => console.log("⚡ Build complete! ⚡")) + .catch(() => process.exit(1)); diff --git a/inst/utils/esbuild.prod.js b/inst/utils/esbuild.prod.js new file mode 100644 index 0000000..4b59fc8 --- /dev/null +++ b/inst/utils/esbuild.prod.js @@ -0,0 +1,24 @@ +import esbuild from "esbuild"; +import {sassPlugin} from 'esbuild-sass-plugin'; +import postcss from 'postcss'; +import autoprefixer from 'autoprefixer'; + +esbuild + .build({ + entryPoints: ["./srcjs/main.js"], + outfile: "inst/<>-<>/dist/<>.min.js", + bundle: true, + format: "esm", + minify: true, // prod + sourcemap: "external", // prod + plugins: [ + sassPlugin({ + async transform(source) { + const { css } = await postcss([autoprefixer]).process(source); + return css; + }, + }), + ] + }) + .then(() => console.log("⚡ Build complete! ⚡")) + .catch(() => process.exit(1)); diff --git a/inst/utils/package.json b/inst/utils/package.json index 6a86e33..952bf5f 100644 --- a/inst/utils/package.json +++ b/inst/utils/package.json @@ -7,16 +7,20 @@ "directories": { "man": "man" }, + "type": "module", "scripts": { "test": "mocha srcjs/test", - "build-dev": "esbuild srcjs/<> --bundle --format=esm --outfile=inst/<>-<>/js/<>.js", - "build-prod": "esbuild srcjs/<> --bundle --minify --sourcemap --format=esm --outfile=inst/<>-<>/js/<>.min.js" + "build-dev": "node esbuild.dev.js", + "build-prod": "node esbuild.prod.js" }, "keywords": [], "author": "", "license": "<>", "devDependencies": { "esbuild": "^0.8.46", + "esbuild-sass-plugin": "^2.4.0", + "postcss": "^8.4.18", + "autoprefixer": "^10.4.13", "mocha": "^8.3.0" } } diff --git a/man/build_js.Rd b/man/build_js.Rd index 054c734..9ee0f91 100644 --- a/man/build_js.Rd +++ b/man/build_js.Rd @@ -4,7 +4,7 @@ \alias{build_js} \title{Compress and optimize all files in the current folder} \usage{ -build_js(dir = "srcjs", mode = c("prod", "dev"), entry_point = "main.js") +build_js(dir = "srcjs", mode = c("prod", "dev")) } \arguments{ \item{dir}{Default to srcjs.} @@ -12,8 +12,6 @@ build_js(dir = "srcjs", mode = c("prod", "dev"), entry_point = "main.js") \item{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).} - -\item{entry_point}{Required internally to setup the esbuild config.} } \description{ Generates a minified file under inst/pkg_name-pkg_version, if mode diff --git a/man/create_file.Rd b/man/create_file.Rd index 7e8f288..b08bbd3 100644 --- a/man/create_file.Rd +++ b/man/create_file.Rd @@ -5,7 +5,7 @@ \alias{create_output_binding} \alias{create_custom_handler} \alias{create_js} -\alias{create_css} +\alias{create_scss} \title{Create a shiny custom input binding boilerplate} \usage{ create_input_binding( @@ -13,7 +13,6 @@ create_input_binding( pkg = ".", dir = "srcjs", open = TRUE, - dir_create = TRUE, initialize = FALSE, dev = FALSE, events = list(name = "click", rate_policy = FALSE), @@ -25,7 +24,6 @@ create_output_binding( pkg = ".", dir = "srcjs", open = TRUE, - dir_create = TRUE, add_reference = TRUE ) @@ -34,23 +32,27 @@ create_custom_handler( pkg = ".", dir = "srcjs", open = TRUE, - dir_create = TRUE, add_reference = TRUE ) create_js( name, - pkg = ".", dir = "srcjs", open = TRUE, - dir_create = TRUE, - with_doc_ready = TRUE, + with_doc_ready = FALSE, template = golem::js_template, ..., add_reference = TRUE ) -create_css(...) +create_scss( + name, + dir = "styles", + open = TRUE, + template = golem::sass_template, + ..., + add_reference = TRUE +) } \arguments{ \item{name}{The name of the module.} @@ -61,8 +63,6 @@ create_css(...) \item{open}{Should the created file be opened?} -\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} - \item{initialize}{For JS file - Whether to add the initialize method. Default to FALSE. Some JavaScript API require to initialize components before using them.} diff --git a/man/reference_style.Rd b/man/reference_style.Rd new file mode 100644 index 0000000..06ab8af --- /dev/null +++ b/man/reference_style.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{reference_style} +\alias{reference_style} +\title{Insert import into main SCSS file} +\usage{ +reference_style(path) +} +\arguments{ +\item{path}{Could be file.name or folder/file.name} +} +\description{ +Useful to reference scss files into the main one. +} +\keyword{internal} diff --git a/man/set_esbuild.Rd b/man/set_esbuild.Rd index 9a7ba81..304ade4 100644 --- a/man/set_esbuild.Rd +++ b/man/set_esbuild.Rd @@ -4,7 +4,12 @@ \alias{set_esbuild} \title{Setup esbuild} \usage{ -set_esbuild() +set_esbuild(light = FALSE) +} +\arguments{ +\item{light}{Used to only install Sass plugins. This is +to workaround a breaking change in charpente where styles does +not exist in old versions.} } \value{ Installs esbuild in node_modules (dev scope), creates srcjs + srcjs/main.js