All Downloads are FREE. Search and download functionalities are using the official Maven repository.

asset.pipeline.processors.Es6Processor.groovy Maven / Gradle / Ivy

Go to download

JVM Asset Pipeline library for serving static web assets, bundling, minifying, and extensibility for transpiling.

There is a newer version: 5.0.1
Show newest version
/*
 * Copyright 2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package asset.pipeline.processors


import asset.pipeline.AbstractProcessor
import asset.pipeline.AssetCompiler
import asset.pipeline.AssetHelper
import asset.pipeline.AssetFile
import asset.pipeline.AssetPipelineConfigHolder
import asset.pipeline.CacheManager
import asset.pipeline.JsAssetFile

import java.util.regex.Pattern
import com.google.javascript.jscomp.*
import com.google.javascript.jscomp.CompilerOptions.LanguageMode

/**
 * This Processor converts ECMAScript 6 syntax to ES5
 *
 * @author David Estes
 */
class Es6Processor extends AbstractProcessor {
	ClassLoader classLoader

	private static final Pattern URL_CALL_PATTERN = ~/goog\.require\((?:\s*)(['"]?)([a-zA-Z0-9\-_.:\/@#?$ &+%=]++)\1?(?:\s*)\)/
	public static ThreadLocal es6JsModules = new ThreadLocal()
	public static ThreadLocal baseModule = new ThreadLocal()

	Es6Processor(final AssetCompiler precompiler) {
		super(precompiler)
		classLoader = getClass().getClassLoader()
	}


	String process(final String inputText, final AssetFile assetFile) {

		if(!inputText) {
			return inputText
		}
		if(assetFile instanceof JsAssetFile) {
			if(!AssetPipelineConfigHolder.config?.enableES6) {
				println "Skipping ES6 transpile"
				return inputText
			}
		}
		def compiler = new Compiler()
		CompilerOptions options = new CompilerOptions()
		options.trustedStrings = true
		CompilationLevel.WHITESPACE_ONLY.setOptionsForCompilationLevel(options);
		options.setLanguageIn(LanguageMode.ECMASCRIPT6)
		options.setLanguageOut(LanguageMode.ECMASCRIPT5)
		options.prettyPrint = true
		options.lineBreak = true
		options.preserveTypeAnnotations = true

		WarningLevel.QUIET.setOptionsForWarningLevel(options);
		String moduleName = AssetHelper.nameWithoutExtension(assetFile.path ?: 'unnamed.js')
		SourceFile sourceFile = SourceFile.fromCode(moduleName, inputText)


		def result = compiler.compile(CommandLineRunner.getDefaultExterns(),[sourceFile] as List,options)
		def output = compiler.toSource()
		if(!output) {
			return inputText
		}

		final Map cachedPaths = [:]
		Boolean originator = false
		if(!baseModule.get()) {
			baseModule.set(assetFile.path)
			originator = true
			es6JsModules.set([])
		}
		try {
			output = output.replaceAll(URL_CALL_PATTERN) { final String urlCall, final String quote, final String assetPath ->
				final Boolean cacheFound = cachedPaths.containsKey(assetPath)
				final String cachedPath = cachedPaths[assetPath]
				String modulePath = assetPath
				String subModuleName = assetPath
				if(assetPath.startsWith('module$')) {
					modulePath = assetPath.substring(7).replace('$','/').replace('_','-')
				}
				String replacementPath
				if (cacheFound) {
					if(cachedPath == null) {
						return "goog.require(${quote}${assetPath}${quote})"
					} else {
						return "goog.require(${quote}${cachedPath}${quote})"
					}
				} else if(assetPath.size() > 0) {
					AssetFile currFile
					if(!assetPath.startsWith('/')) {
						def relativeFileName = [ assetFile.parentPath, modulePath ].join( AssetHelper.DIRECTIVE_FILE_SEPARATOR )
						currFile = AssetHelper.fileForUri(relativeFileName,'application/javascript')
					}

					if(!currFile) {
						currFile = AssetHelper.fileForUri(modulePath,'application/javascript')
					}

					if(!currFile) {
						currFile = AssetHelper.fileForUri(modulePath + '/' + modulePath,'application/javascript')
					}
					if(!currFile) {
						cachedPaths[assetPath] = null
						return "goog.require(${quote}${assetPath}${quote})"
					} else {
						currFile.baseFile = assetFile.baseFile ?: assetFile
						appendModule(currFile, subModuleName, assetFile)
						String path = AssetHelper.fileNameWithoutExtensionFromArtefact(currFile.path, currFile)
						cachedPaths[assetPath] = assetPath
						return "goog.require(${quote}${assetPath}${quote})"
					}
				} else {
					return "require(${quote}${assetPath}${quote})"
				}
			}

			if(baseModule.get() == assetFile.path && es6JsModules.get()) {
				def googBaseJsResource = classLoader.getResource('asset/pipeline/goog/base.js')
				output = googBaseJsResource.text + "\n" + es6Runtime() + modulesJs() + output
			}
			else if(baseModule.get() == assetFile.path) {
				def googBaseJsResource = classLoader.getResource('asset/pipeline/goog/base.js')
				output = googBaseJsResource.text + "\n" + es6Runtime() + output
			}	
			
		} finally {
			if(originator) {
				es6JsModules.set([])
				baseModule.set(null)
			}
		}

		AssetFile baseFile = assetFile.baseFile ?: assetFile
		if(!baseFile.matchedDirectives.find{it == '__goog_base'}) {
			
		}
		return output
	}

	private appendModule(AssetFile assetFile, String moduleName, AssetFile currentFile) {
		List moduleList = es6JsModules.get()
		if(!moduleList) {
			moduleList = []
			es6JsModules.set(moduleList)
		}

		if(moduleList.find{Map row -> row.name == assetFile.path}) {
			return
		}
		Map moduleMap = [:] as Map
		moduleMap.name = assetFile.path
		moduleMap.value = ' \n'
		def insertPosition = null
		moduleList.eachWithIndex{ row, index ->
			if(row.name == currentFile.path) {
				insertPosition = index
			}
		}
		if(insertPosition != null) {
			moduleList.add(insertPosition,moduleMap)	
		} else {
			moduleList << moduleMap	
		}
		moduleMap.value = encapsulateModule(assetFile, moduleName)
		CacheManager.addCacheDependency(baseModule.get(), assetFile)
	}

	private String modulesJs() {
		String output = ''

		output += es6JsModules.get()?.collect { Map row ->
			"${row.value};"
		}.join('\n')

		return output
	}


	private encapsulateModule(AssetFile assetFile, moduleName) {
		String output = assetFile.processedStream(precompiler,true)
		if(!output.contains(moduleName)) {
			//its not an ES6 module we need to wrap it in a require js module syntax
					String encapsulation = """
goog.provide('${moduleName}');
(function() {
  var module = {exports: {}};
  var exports = module.exports;

  ${output}

  ${moduleName} = {default:module['exports']};
  return module;
})()
"""
			return encapsulation
		} else {
			return output
		}
	}



	private String es6Runtime() {
		def es6RuntimeJsResource = classLoader.getResource('asset/pipeline/goog/es6_runtime.js')
		return es6RuntimeJsResource.text + "\n" + "var _asset_pipeline_loaded_modules = _asset_pipeline_loaded_modules || {};\n"
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy