You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by jk...@apache.org on 2014/06/18 17:12:52 UTC
git commit: TAP5-2350: add an AMDWrapper class that can be used to
wrap plain JavaScript libraries as AMD modules
Repository: tapestry-5
Updated Branches:
refs/heads/master 3ac532438 -> a08fd3bd1
TAP5-2350: add an AMDWrapper class that can be used to wrap plain JavaScript libraries as AMD modules
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/a08fd3bd
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/a08fd3bd
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/a08fd3bd
Branch: refs/heads/master
Commit: a08fd3bd19dfece2b818a0746af828f091277265
Parents: 3ac5324
Author: Jochen Kemnade <jo...@eddyson.de>
Authored: Wed Jun 18 16:59:47 2014 +0200
Committer: Jochen Kemnade <jo...@eddyson.de>
Committed: Wed Jun 18 17:12:35 2014 +0200
----------------------------------------------------------------------
.../services/javascript/AMDWrapper.java | 255 +++++++++++++++++++
.../JavaScriptModuleConfiguration.java | 1 +
.../services/javascript/ModuleManager.java | 1 +
.../services/javascript/AMDWrapperSpec.groovy | 65 +++++
4 files changed, 322 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a08fd3bd/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/AMDWrapper.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/AMDWrapper.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/AMDWrapper.java
new file mode 100644
index 0000000..3783a65
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/AMDWrapper.java
@@ -0,0 +1,255 @@
+// Copyright 2014 The Apache Software Foundation
+//
+// 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 org.apache.tapestry5.services.javascript;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.net.URL;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Vector;
+
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Flow;
+import org.apache.tapestry5.func.Mapper;
+import org.apache.tapestry5.func.Predicate;
+import org.apache.tapestry5.internal.util.VirtualResource;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+
+/**
+ * Used to wrap plain JavaScript libraries as AMD modules. The underlying
+ * resource is transformed before it is sent to the client.
+ * <p>
+ * This is an alternative to configuring RequireJS module shims for the
+ * libraries. As opposed to shimmed libraries, the modules created using the
+ * AMDWrapper can be added to JavaScript stacks.
+ * <p>
+ * If the library depends on global variables, these can be added as module
+ * dependencies. For a library that expects jQuery to be available as
+ * <code>$<code>, the wrapper should be setup calling <code>require("jQuery", "$")<code>
+ * on the respective wrapper.
+ *
+ * @since 5.4
+ * @see JavaScriptModuleConfiguration
+ * @see ModuleManager
+ */
+public class AMDWrapper {
+
+ /**
+ * The underlying resource, usually a JavaScript library
+ */
+ private final Resource resource;
+
+ /**
+ * The modules that this module requires, the keys being module names and
+ * the values being the respective parameter names for the module's factory
+ * function.
+ */
+ private final Map<String, String> requireConfig = new LinkedHashMap<String, String>();
+
+ /**
+ * The expression that determines what is returned from the factory function
+ */
+ private String returnExpression;
+
+ public AMDWrapper(final Resource resource) {
+ this.resource = resource;
+ }
+
+ /**
+ * Add a dependency on another module. The module will be passed into the
+ * generated factory function as a parameter.
+ *
+ * @param moduleName
+ * the name of the required module, e.g. <code>jQuery</code>
+ * @param parameterName
+ * the module's corresponding parameter name of the factory
+ * function, e.g. <code>$</code>
+ * @return this AMDWrapper for further configuration
+ */
+ public AMDWrapper require(final String moduleName,
+ final String parameterName) {
+ requireConfig.put(moduleName, parameterName);
+ return this;
+ }
+
+ /**
+ * Add a dependency on another module. The module will be loaded but not
+ * passed to the factory function. This is useful for dependencies on other
+ * modules that do not actually return a value.
+ *
+ * @param moduleName
+ * the name of the required module, e.g.
+ * <code>bootstrap/transition</code>
+ * @return this AMDWrapper for further configuration
+ */
+ public AMDWrapper require(final String moduleName) {
+ requireConfig.put(moduleName, null);
+ return this;
+ }
+
+ /**
+ * Optionally sets a return expression for this module. If the underlying
+ * library creates a global variable, this is usually what is returned here.
+ *
+ * @param returnExpression
+ * the expression that is returned from this module (e.g.
+ * <code>Raphael</code>)
+ * @return this AMDWrapper for further configuration
+ */
+ public AMDWrapper setReturnExpression(final String returnExpression) {
+ this.returnExpression = returnExpression;
+ return this;
+ }
+
+ /**
+ * Return this wrapper instance as a {@link JavaScriptModuleConfiguration},
+ * so it can be contributed to the {@link ModuleManager}'s configuration.
+ * The resulting {@link JavaScriptModuleConfiguration} should not be
+ * changed.
+ *
+ * @return a {@link JavaScriptModuleConfiguration} for this AMD wrapper
+ */
+ public JavaScriptModuleConfiguration asJavaScriptModuleConfiguration() {
+ return new JavaScriptModuleConfiguration(transformResource());
+ }
+
+ private Resource transformResource() {
+ return new AMDModuleWrapperResource(resource, requireConfig,
+ returnExpression);
+ }
+
+ /**
+ * A virtual resource that wraps a plain JavaScript library as an AMD
+ * module.
+ *
+ */
+ private final static class AMDModuleWrapperResource extends VirtualResource {
+ private final Resource resource;
+ private final Map<String, String> requireConfig;
+ private final String returnExpression;
+
+ public AMDModuleWrapperResource(final Resource resource,
+ final Map<String, String> requireConfig,
+ final String returnExpression) {
+ this.resource = resource;
+ this.requireConfig = requireConfig;
+ this.returnExpression = returnExpression;
+
+ }
+
+ @Override
+ public InputStream openStream() throws IOException {
+ InputStream leaderStream;
+ InputStream trailerStream;
+
+ StringBuilder sb = new StringBuilder();
+
+ // create a Flow of map entries (module name to factory function
+ // parameter name)
+ Flow<Entry<String, String>> requiredModulesToNames = F
+ .flow(requireConfig.entrySet());
+
+ // some of the modules are not passed to the factory, sort them last
+ Flow<Entry<String, String>> requiredModulesToNamesNamedFirst = requiredModulesToNames
+ .remove(VALUE_IS_NULL).concat(
+ requiredModulesToNames.filter(VALUE_IS_NULL));
+
+ sb.append("define([");
+ sb.append(InternalUtils.join(requiredModulesToNamesNamedFirst
+ .map(GET_KEY).map(QUOTE).toList()));
+ sb.append("], function(");
+
+ // append only the modules that should be passed to the factory
+ // function, i.e. those whose map entry value is not null
+ sb.append(InternalUtils.join(F.flow(requireConfig.values())
+ .filter(F.notNull()).toList()));
+ sb.append("){\n");
+ leaderStream = toInputStream(sb);
+ sb.setLength(0);
+
+ if (returnExpression != null)
+ {
+ sb.append("\nreturn ");
+ sb.append(returnExpression);
+ sb.append(";");
+ }
+ sb.append("\n});");
+ trailerStream = toInputStream(sb);
+
+ Vector<InputStream> v = new Vector<InputStream>(3);
+ v.add(leaderStream);
+ v.add(resource.openStream());
+ v.add(trailerStream);
+
+ return new SequenceInputStream(v.elements());
+ }
+
+ @Override
+ public String getFile() {
+ return "generated-module-for-" + resource.getFile();
+ }
+
+ @Override
+ public URL toURL() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "AMD module wrapper for " + resource.toString();
+ }
+
+ private static InputStream toInputStream(final StringBuilder sb) {
+ return new ByteArrayInputStream(sb.toString().getBytes(UTF8));
+
+ }
+ }
+
+ private final static Mapper<Entry<String, String>, String> GET_KEY = new Mapper<Entry<String, String>, String>() {
+
+ @Override
+ public String map(final Entry<String, String> element) {
+ return element.getKey();
+ }
+
+ };
+
+ private final static Predicate<Entry<String, String>> VALUE_IS_NULL = new Predicate<Entry<String, String>>() {
+
+ @Override
+ public boolean accept(final Entry<String, String> element) {
+ return element.getValue() == null;
+ }
+
+ };
+
+ private final static Mapper<String, String> QUOTE = new Mapper<String, String>() {
+
+ @Override
+ public String map(final String element) {
+ StringBuilder sb = new StringBuilder(element.length() + 2);
+ sb.append('"');
+ sb.append(element);
+ sb.append('"');
+ return sb.toString();
+ }
+ };
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a08fd3bd/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModuleConfiguration.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModuleConfiguration.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModuleConfiguration.java
index f719b87..2fb1478 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModuleConfiguration.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModuleConfiguration.java
@@ -33,6 +33,7 @@ import java.util.List;
* module will be satisfied by the resource.'
*
* @since 5.4
+ * @see AMDWrapper
*/
public final class JavaScriptModuleConfiguration
{
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a08fd3bd/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java
index f7d5487..5496133 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java
@@ -29,6 +29,7 @@ import java.util.List;
*
* @since 5.4
* @see ModuleConfigurationCallback
+ * @see AMDWrapper
*/
@UsesMappedConfiguration(JavaScriptModuleConfiguration.class)
public interface ModuleManager
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a08fd3bd/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/AMDWrapperSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/AMDWrapperSpec.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/AMDWrapperSpec.groovy
new file mode 100644
index 0000000..802f932
--- /dev/null
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/AMDWrapperSpec.groovy
@@ -0,0 +1,65 @@
+package org.apache.tapestry5.services.javascript
+
+import org.apache.tapestry5.ioc.Resource
+
+import spock.lang.Specification
+
+class AMDWrapperSpec extends Specification {
+
+ def "AMD wrapper without dependencies"(){
+ setup:
+ Resource resource = Mock()
+ when:
+ def wrapper = new AMDWrapper(resource)
+ def moduleConfiguration = wrapper.asJavaScriptModuleConfiguration()
+ then:
+ !moduleConfiguration.needsConfiguration
+ when:
+ def amdModuleContent = moduleConfiguration.resource.openStream().text
+ then:
+ amdModuleContent ==
+ """define([], function(){
+alert('Hello World!');
+});"""
+ 1 * resource.openStream() >> new ByteArrayInputStream("alert('Hello World!');".bytes)
+ }
+
+ def "AMD wrapper with named dependencies"(){
+ setup:
+ Resource resource = Mock()
+ when:
+ def wrapper = new AMDWrapper(resource)
+ wrapper.require("jquery", '$')
+ def moduleConfiguration = wrapper.asJavaScriptModuleConfiguration()
+ then:
+ !moduleConfiguration.needsConfiguration
+ when:
+ def amdModuleContent = moduleConfiguration.resource.openStream().text
+ then:
+ amdModuleContent ==
+ '''define(["jquery"], function($){
+$("body").css("background-color", "pink");
+});'''
+ 1 * resource.openStream() >> new ByteArrayInputStream('$("body").css("background-color", "pink");'.bytes)
+ }
+
+ def "AMD wrapper with return expression"(){
+ setup:
+ Resource resource = Mock()
+ when:
+ def wrapper = new AMDWrapper(resource)
+ wrapper.setReturnExpression("myImportantVar")
+ def moduleConfiguration = wrapper.asJavaScriptModuleConfiguration()
+ then:
+ !moduleConfiguration.needsConfiguration
+ when:
+ def amdModuleContent = moduleConfiguration.resource.openStream().text
+ then:
+ amdModuleContent ==
+ '''define([], function(){
+var myImportantVar = 42;
+return myImportantVar;
+});'''
+ 1 * resource.openStream() >> new ByteArrayInputStream('var myImportantVar = 42;'.bytes)
+ }
+}