Skip to content

Commit

Permalink
Added the inline pipeline (#98)
Browse files Browse the repository at this point in the history
Added the inline pipeline & updated CHANGELOG.md and README.md

The inline pipeline allows including the content of files directly into `index.html`. Just add `<link data-trunk rel="inline" href="path/to/file">` to the position in your `index.html` where the content of the files should be placed, and you're good to go. There three content types available: `html`, `css`, and `js`. `html` is pasted into `index.html` as is, `css` is wrapped in `style` tags and `js` in wrapped in `script` tags.
The type can also be specified with the `type` attribute.
  • Loading branch information
DzenanJupic committed Feb 2, 2021
1 parent 7e1662b commit 24b7ab6
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe
### added
- Closed [#93](https://github.com/thedodd/trunk/issues/93): The `watch` and `serve` subcommands can now watch specific folder(s) or file(s) through the new `--watch <path>...` option.

## 0.8.0
### added
- Inline the content of files directly into `index.html` with the new `inline` asset. There are three content types that can be inlined: `html`, `css`, and `js`. The type can be specified with the `type` attribute or is inferred by the file extension.

## 0.7.4
### fixed
- Fixed a regression in Trunk CLI help output, where incorrect help info was being displayed.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ Currently supported asset types:
-`css`: Trunk will copy linked css files found in the source HTML without content modification. This content is hashed for cache control. The `href` attribute must be included in the link pointing to the css file to be processed.
- In the future, Trunk will resolve local `@imports`, will handle minification (see [trunk#7](https://github.com/thedodd/trunk/issues/3)), and we may even look into a pattern where any CSS found in the source tree will be bundled, which would enable a nice zero-config "component styles" pattern. See [trunk#3](https://github.com/thedodd/trunk/issues/3) for more details.
-`icon`: Trunk will copy the icon image specified in the `href` attribute to the `dist` dir. This content is hashed for cache control.
-`inline`: Trunk will inline the content of the file specified in the `href` attribute into `index.html`. This content is copied exactly, no hashing is performed.
- `type`: (optional) either `html`, `css`, or `js`. If not present, the type is inferred by the file extension. `css` is wrapped in `style` tags, while
`js` is wrapped in `script` tags.
-`copy-file`: Trunk will copy the file specified in the `href` attribute to the `dist` dir. This content is copied exactly, no hashing is performed.
-`copy-dir`: Trunk will recursively copy the directory specified in the `href` attribute to the `dist` dir. This content is copied exactly, no hashing is performed.
-`rust-worker`: (in-progress) Trunk will compile the specified Rust project as a WASM web worker. The following attributes are required:
Expand Down
122 changes: 122 additions & 0 deletions src/pipelines/inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//! Inline asset pipeline.

use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;

use anyhow::{anyhow, bail, Result};
use async_std::task::{spawn, JoinHandle};
use indicatif::ProgressBar;
use nipper::{Document, Selection};

use super::{AssetFile, TrunkLinkPipelineOutput, ATTR_HREF, ATTR_TYPE};

/// An Inline asset pipeline.
pub struct Inline {
/// The ID of this pipeline's source HTML element.
id: usize,
/// The progress bar to use for this pipeline.
progress: ProgressBar,
/// The asset file being processed.
asset: AssetFile,
/// The type of the asset file that determines how the content of the file
/// is inserted into `index.html`.
content_type: ContentType,
}

impl Inline {
pub const TYPE_INLINE: &'static str = "inline";

pub async fn new(progress: ProgressBar, html_dir: Arc<PathBuf>, el: Selection<'_>, id: usize) -> Result<Self> {
let href_attr = el
.attr(ATTR_HREF)
.ok_or_else(|| anyhow!("required attr `href` missing for <link data-trunk .../> element: {}", el.html()))?;

let mut path = PathBuf::new();
path.extend(href_attr.as_ref().split('/'));

let asset = AssetFile::new(&html_dir, path).await?;
let content_type = ContentType::from_attr_or_ext(el.attr(ATTR_TYPE), &asset.ext)?;

Ok(Self {
id,
progress,
asset,
content_type,
})
}

/// Spawn the pipeline for this asset type.
pub fn spawn(self) -> JoinHandle<Result<TrunkLinkPipelineOutput>> {
spawn(async move {
self.progress.set_message("reading file content");
let content = self.asset.read_to_string().await?;
self.progress.set_message("finished reading file content");
Ok(TrunkLinkPipelineOutput::Inline(InlineOutput {
id: self.id,
content,
content_type: self.content_type,
}))
})
}
}

/// The content type of a inlined file.
pub enum ContentType {
/// Html is just pasted into `index.html` as is.
Html,
/// CSS is wrapped into `style` tags.
CSS,
/// JS is wrapped into `script` tags.
JS,
}

impl ContentType {
/// Either tries to parse the provided attribute to a ContentType
/// or tries to infer the ContentType from the AssetFile extension.
fn from_attr_or_ext(attr: Option<impl AsRef<str>>, ext: &str) -> Result<Self> {
match attr {
Some(attr) => Self::from_str(attr.as_ref()),
None => Self::from_str(ext),
}
}
}

impl FromStr for ContentType {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"html" => Ok(Self::Html),
"css" => Ok(Self::CSS),
"js" => Ok(Self::JS),
s => bail!(
r#"unknown `type="{}"` value for <link data-trunk rel="inline" .../> attr; please ensure the value is lowercase and is a supported content type"#,
s
),
}
}
}

/// The output of a Inline build pipeline.
pub struct InlineOutput {
/// The ID of this pipeline.
pub id: usize,
/// The content of the target file.
pub content: String,
/// The content type of the target file.
pub content_type: ContentType,
}

impl InlineOutput {
pub async fn finalize(self, dom: &mut Document) -> Result<()> {
let html = match self.content_type {
ContentType::Html => self.content,
ContentType::CSS => format!("<style>{}</style>", self.content),
ContentType::JS => format!("<script>{}</script>", self.content),
};

dom.select(&super::trunk_id_selector(self.id)).replace_with_html(html);
Ok(())
}
}
29 changes: 22 additions & 7 deletions src/pipelines/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod copyfile;
mod css;
mod html;
mod icon;
mod inline;
mod rust_app;
mod rust_worker;
mod sass;
Expand All @@ -23,13 +24,15 @@ use crate::pipelines::copydir::{CopyDir, CopyDirOutput};
use crate::pipelines::copyfile::{CopyFile, CopyFileOutput};
use crate::pipelines::css::{Css, CssOutput};
use crate::pipelines::icon::{Icon, IconOutput};
use crate::pipelines::inline::{Inline, InlineOutput};
use crate::pipelines::rust_app::{RustApp, RustAppOutput};
use crate::pipelines::rust_worker::{RustWorker, RustWorkerOutput};
use crate::pipelines::sass::{Sass, SassOutput};

pub use html::HtmlPipeline;

const ATTR_HREF: &str = "href";
const ATTR_TYPE: &str = "type";
const ATTR_REL: &str = "rel";
const SNIPPETS_DIR: &str = "snippets";
const TRUNK_ID: &str = "data-trunk-id";
Expand All @@ -45,6 +48,7 @@ pub enum TrunkLink {
Css(Css),
Sass(Sass),
Icon(Icon),
Inline(Inline),
CopyFile(CopyFile),
CopyDir(CopyDir),
RustApp(RustApp),
Expand All @@ -60,13 +64,14 @@ impl TrunkLink {
.attr(ATTR_REL)
.ok_or_else(|| anyhow!("all <link data-trunk .../> elements must have a `rel` attribute indicating the asset type"))?;
Ok(match rel.as_ref() {
Sass::TYPE_SASS | Sass::TYPE_SCSS => Self::Sass(Sass::new(cfg.clone(), progress, html_dir, el, id).await?),
Icon::TYPE_ICON => Self::Icon(Icon::new(cfg.clone(), progress, html_dir, el, id).await?),
Css::TYPE_CSS => Self::Css(Css::new(cfg.clone(), progress, html_dir, el, id).await?),
CopyFile::TYPE_COPY_FILE => Self::CopyFile(CopyFile::new(cfg.clone(), progress, html_dir, el, id).await?),
CopyDir::TYPE_COPY_DIR => Self::CopyDir(CopyDir::new(cfg.clone(), progress, html_dir, el, id).await?),
RustApp::TYPE_RUST_APP => Self::RustApp(RustApp::new(cfg.clone(), progress, html_dir, ignore_chan, el, id).await?),
RustWorker::TYPE_RUST_WORKER => Self::RustWorker(RustWorker::new(cfg.clone(), progress, html_dir, ignore_chan, el, id).await?),
Sass::TYPE_SASS | Sass::TYPE_SCSS => Self::Sass(Sass::new(cfg, progress, html_dir, el, id).await?),
Icon::TYPE_ICON => Self::Icon(Icon::new(cfg, progress, html_dir, el, id).await?),
Inline::TYPE_INLINE => Self::Inline(Inline::new(progress, html_dir, el, id).await?),
Css::TYPE_CSS => Self::Css(Css::new(cfg, progress, html_dir, el, id).await?),
CopyFile::TYPE_COPY_FILE => Self::CopyFile(CopyFile::new(cfg, progress, html_dir, el, id).await?),
CopyDir::TYPE_COPY_DIR => Self::CopyDir(CopyDir::new(cfg, progress, html_dir, el, id).await?),
RustApp::TYPE_RUST_APP => Self::RustApp(RustApp::new(cfg, progress, html_dir, ignore_chan, el, id).await?),
RustWorker::TYPE_RUST_WORKER => Self::RustWorker(RustWorker::new(cfg, progress, html_dir, ignore_chan, el, id).await?),
_ => bail!(
r#"unknown <link data-trunk .../> attr value `rel="{}"`; please ensure the value is lowercase and is a supported asset type"#,
rel.as_ref()
Expand All @@ -80,6 +85,7 @@ impl TrunkLink {
TrunkLink::Css(inner) => inner.spawn(),
TrunkLink::Sass(inner) => inner.spawn(),
TrunkLink::Icon(inner) => inner.spawn(),
TrunkLink::Inline(inner) => inner.spawn(),
TrunkLink::CopyFile(inner) => inner.spawn(),
TrunkLink::CopyDir(inner) => inner.spawn(),
TrunkLink::RustApp(inner) => inner.spawn(),
Expand All @@ -93,6 +99,7 @@ pub enum TrunkLinkPipelineOutput {
Css(CssOutput),
Sass(SassOutput),
Icon(IconOutput),
Inline(InlineOutput),
CopyFile(CopyFileOutput),
CopyDir(CopyDirOutput),
RustApp(RustAppOutput),
Expand All @@ -106,6 +113,7 @@ impl TrunkLinkPipelineOutput {
TrunkLinkPipelineOutput::Css(out) => out.finalize(dom).await,
TrunkLinkPipelineOutput::Sass(out) => out.finalize(dom).await,
TrunkLinkPipelineOutput::Icon(out) => out.finalize(dom).await,
TrunkLinkPipelineOutput::Inline(out) => out.finalize(dom).await,
TrunkLinkPipelineOutput::CopyFile(out) => out.finalize(dom).await,
TrunkLinkPipelineOutput::CopyDir(out) => out.finalize(dom).await,
TrunkLinkPipelineOutput::RustApp(out) => out.finalize(dom).await,
Expand Down Expand Up @@ -196,6 +204,13 @@ impl AssetFile {
.with_context(|| format!("error copying file {:?} to {:?}", &self.path, &file_path))?;
Ok(HashedFileOutput { hash, file_path, file_name })
}

/// Read the content of this asset to a String.
pub async fn read_to_string(&self) -> Result<String> {
async_std::fs::read_to_string(&self.path)
.await
.with_context(|| format!("error reading file {:?} to string", self.path))
}
}

/// The output of a hashed file.
Expand Down

0 comments on commit 24b7ab6

Please sign in to comment.