123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- /*
- expansive.es - Configuration for exp-css
- Transform by prefixing and minifying. Uses postcss and less.
- */
- Expansive.load({
- services: {
- name: 'css',
- dotmin: true,
- files: [ '**.css*', '!**.map', '!*.less*' ],
- force: false,
- extract: false,
- minify: false,
- prefix: true,
- usemap: true,
- usemin: true,
- transforms: [{
- mappings: {
- 'css',
- 'min.css'
- 'css.map'
- },
- init: function(transform) {
- let service = transform.service
- if (service.minify) {
- service.usemin ||= true
- }
- if (service.usemap) {
- service.usemin ||= true
- }
- if (service.files) {
- if (!(service.files is Array)) {
- service.files = [ service.files ]
- }
- if (expansive.control.collections.styles.length == 0) {
- expansive.control.collections.styles = service.files
- }
- }
- if (!service.minify) {
- expansive.transforms['css-minify'].enable = false
- }
- if (!service.extract) {
- expansive.transforms['css-extract'].enable = false
- }
- }
- resolve: function(path: Path, transform): Path? {
- let service = transform.service
- let vfile = directories.contents.join(path)
- if (vfile.endsWith('.min.css')) {
- let base = vfile.trimEnd('.min.css')
- if (service.usemin && !(service.minify && service.force && base.joinExt('css', true).exists)) {
- if (service.usemap) {
- if (base.joinExt('css.map', true).exists) {
- return terminal(path)
- }
- } else {
- return terminal(path)
- }
- }
- } else if (vfile.endsWith('.css.map')) {
- if (service.usemin) {
- let base = vfile.trimEnd('.css.map')
- if (service.usemap && !(service.minify && service.force && base.joinExt('css', true).exists)) {
- if (base.joinExt('min.css', true).exists) {
- return terminal(path)
- }
- }
- }
- } else {
- let minified = vfile.replaceExt('min.css')
- /*
- Use this source file if forced+miify, or a suitable minfied version does not exist or !usemin
- */
- if ((service.minify && service.force) ||
- !(minified.exists && service.usemin && (!service.usemap || vfile.replaceExt('css.map').exists))) {
- if (service.minify && service.dotmin) {
- return terminal(path.trimExt().joinExt('min.css', true))
- }
- return terminal(path)
- }
- }
- return null
- }
- }, {
- name: 'prefix',
- mappings: 'css',
- render: function(contents) {
- let postcss = Cmd.locate('postcss')
- if (postcss) {
- contents = expansive.run(postcss + ' --use autoprefixer', contents)
- } else {
- trace('Warn', 'Cannot find postcss')
- }
- return contents
- }
- }, {
- name: 'minify',
- mappings: 'css',
- render: function(contents, meta) {
- trace('Minify', meta.current)
- let less = Cmd.locate('lessc')
- if (less) {
- contents = expansive.run(less + ' --compress - ', contents, meta)
- } else {
- trace('Warn', 'Cannot find lessc')
- }
- return contents
- }
- }, {
- name: 'render',
- mappings: {
- 'css',
- 'min.css'
- },
- init: function(transform) {
- let service = transform.service
- service.hash = {}
- /*
- Render styles is based on 'collections.styles' which defaults to '**.css' and is modified via expansive.json and addItems.
- */
- global.renderStyles = function(filter = null, extras = []) {
- let collections = expansive.collections
- if (!collections.styles) {
- return
- }
- function buildStyleList(files: Array): Array {
- let directories = expansive.directories
- let service = expansive.services.css
- let styles = []
- for each (style in files) {
- if ((style = expansive.getDestPath(style)) == null) {
- continue
- }
- let vfile = directories.contents.join(style)
- let base = vfile.trimEnd('.min.css').trimEnd('.css')
- let map = base.joinExt('min.map', true).exists || base.joinExt('css.map', true).exists ||
- base.joinExt('.min.css.map', true).exists
- if (vfile.endsWith('min.css')) {
- if (service.usemin && (!service.usemap || map)) {
- styles.push(style)
- }
- } else {
- let minified = vfile.replaceExt('min.css').exists
- let map = vfile.replaceExt('min.map').exists || vfile.replaceExt('css.map').exists ||
- vfile.replaceExt('.min.css.map').exists
- if ((service.minify && service.force) || !minified || !(service.usemap && map)) {
- if (service.minify && service.dotmin) {
- styles.push(style.replaceExt('min.css'))
- } else {
- styles.push(style)
- }
- }
- }
- }
- return styles
- }
- let directories = expansive.directories
- let service = expansive.services.css
- /*
- Pages have different stylesheets and so must compute style list per page.
- This is hased and saved.
- */
- if (!service.hash[collections.styles]) {
- let files = directories.contents.files(collections.styles,
- { contents: true, directories: false, relative: true})
- if (expansive.control.filters) {
- files = files.filter(function(e) { return e.glob(expansive.control.filters) })
- }
- files = expansive.orderFiles(files, "css")
- service.hash[collections.styles] = buildStyleList(files).unique()
- }
- for each (style in service.hash[collections.styles]) {
- if (filter && !Path(style).glob(filter)) {
- continue
- }
- if (service.absolute) {
- if (!style.startsWith('http') && !style.startsWith('..')) {
- style = '/' + style
- }
- } else {
- style = meta.top.join(style).trimStart('./')
- }
- write('<link href="' + style + '" rel="stylesheet" type="text/css" />\n ')
- }
- if (extras && extras is String) {
- extras = [extras]
- }
- if (service.states) {
- let extracted = service.states[meta.destPath]
- if (extracted && extracted.link) {
- let ct = expansive.transforms.css
- extras.push(ct.resolve(extracted.link, ct))
- }
- }
- for each (style in extras) {
- if (service.absolute) {
- if (!style.startsWith('http') && !style.startsWith('..')) {
- style = '/' + style
- }
- } else {
- style = meta.top.join(style).trimStart('./')
- }
- write('<link href="' + style + '" rel="stylesheet" type="text/css" />\n ')
- }
- if (expansive.collections['inline-styles']) {
- write('<style>')
- for each (style in expansive.collections['inline-styles']) {
- write(style)
- }
- write('\n </style>')
- }
- }
- },
- pre: function (transform) {
- if (expansive.modified.everything) {
- transform.service.hash = {}
- }
- },
- }, {
- name: 'extract',
- mappings: 'html',
- init: function(transform) {
- let service = transform
- if (!service.extract) {
- transform.enable = false
- return
- }
- transform.nextId = 0
- service.states = {}
- },
- render: function(contents, meta, transform) {
- /*
- Local function to handle inline style elements
- */
- function handleStyleElements(contents, meta, state, transform): String {
- let re = /<style[^>]*>(.*)<\/style>/smg
- let start = 0, end = 0
- let output = ''
- let matches
- while (matches = re.exec(contents, start)) {
- let elt = matches[0]
- end = re.lastIndex - elt.length
- output += contents.slice(start, end)
- state.elements ||= []
- state.elements.push(matches[1].trimEnd(';'))
- start = re.lastIndex
- }
- output += contents.slice(start)
- return output
- }
- /*
- Local function to handle style attributes
- */
- function handleStyleAttributes(contents, meta, state, transform): String {
- let result = ''
- let re = /<([\w_\-:.]+)([^>]*>)/g
- let start = 0, end = 0
- let matches
- while (matches = re.exec(contents, start)) {
- let elt = matches[0]
- end = re.lastIndex - matches[0].length
- /* Emit prior contents */
- result += contents.slice(start, end)
- let attre = /(.*) +(style=)(["'])(.*?)(\3)(.*)/m
- if ((cmatches = attre.exec(elt)) != null) {
- elt = cmatches[1] + cmatches[6]
- let id
- if ((ematches = elt.match(/id=['"]([^'"]+)['"]/)) != null) {
- id = '#' + ematches[1]
- } else {
- id = 'exp-' + ++transform.nextId
- }
- state.attributes ||= {}
- state.attributes[id] = cmatches[4].trimEnd(';')
- if (!id.startsWith('#')) {
- let classre = /(.*) +(class=)(["'])([^'"]+)(\3)(.*)/m
- if ((classes = classre.exec(elt)) != null) {
- elt = classes[1] + ' ' + classes[2] + '"' + classes[4] + ' ' + id + '"' + classes[6]
- } else {
- elt = cmatches[1] + ' class="' + id + '" ' + cmatches[6]
- }
- } else {
- elt = cmatches[1] + cmatches[6]
- }
- }
- result += elt
- start = re.lastIndex
- }
- return result + contents.slice(start)
- }
- let service = transform.service
- let state = {}
- contents = handleStyleElements(contents, meta, state, transform)
- contents = handleStyleAttributes(contents, meta, state, transform)
- if (state.elements || state.attributes) {
- let ss = service.states[meta.destPath] ||= {}
- if (service.extract === true) {
- ss.link = meta.destPath.replaceExt('css')
- } else {
- ss.link = service.extract
- }
- if (state.elements) {
- ss.elements ||= []
- ss.elements += state.elements
- vtrace('Extract', 'Scripts from ' + meta.document)
- }
- if (state.attributes) {
- ss.attributes ||= {}
- blend(ss.attributes, state.attributes)
- vtrace('Extract', 'Onclick from ' + meta.document)
- }
- }
- return contents
- },
- /*
- Post process and create external stylesheets for inline styles
- */
- post: function(transform) {
- let service = transform.service
- let perdoc = (service.extract !== true)
- let styles = '/*\n Inline styles\n */\n'
- for (let [file, state] in service.states) {
- if (perdoc) {
- styles = '/*\n Inline styles for ' + file + '\n */\n'
- }
- if (state.elements) {
- for (let [key,value] in state.elements) {
- value = value.trim().split('\n').transform(function (e) e.trim()).join('\n ')
- // Match {{
- value = value.replace(' }', '}')
- state.elements[key] = value
- }
- styles += state.elements.join('\n\n') + '\n\n'
- }
- for (let [id, code] in state.attributes) {
- styles += id + ' {\n ' + code + '\n}\n\n'
- }
- if (perdoc) {
- let destPath = Path(file).replaceExt('css')
- let dest = directories.dist.join(destPath)
- let meta = blend(expansive.topMeta.clone(), { document: destPath, layout: null })
- styles = renderContents(styles, meta)
- writeDest(styles, meta)
- }
- }
- if (!perdoc) {
- let destPath = service.extract
- let dest = directories.dist.join(destPath)
- let meta = blend(expansive.topMeta.clone(), { document: destPath, layout: null })
- styles = renderContents(styles, meta)
- writeDest(styles, meta)
- }
- }
- }]
- }
- })
|