Tuesday, May 23, 2006

How to Package GWT Components

This is a response I sent to the Google Web Toolkit group in response to questions about how to package user created components into a JAR file for reuse. The full message thread can be found in Google Groups.

I created a widget called "Heading" which wraps the text in the appropriate
<h1></h1> HTML tag.

The contents of the JAR file look like this:

 META-INF/
META-INF/MANIFEST.MF
org/
org/hanson/
org/hanson/gwt/
org/hanson/gwt/client/
org/hanson/gwt/client/Heading.class
org/hanson/gwt/client/Heading.java
org/hanson/gwt/Components.gwt.xml

The widget class AND source must be in the "client" package (you can change
this in the gwt.xml file if needed). The source must be present for the
compiler to generate the JavaScript.

The structure of the JAR, in general, should be like this:

some.package.MyName.gwt.xml (project properties)
some.package.client.* (code to be compiled to JavaScript)
some.package.public.* (images or other files)
some.package.xyz.* (anything else)

==== The source for the Heading widget ====


package org.hanson.gwt.client;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Widget;

public class Heading extends Widget
{
public Heading ()
{
setElement(DOM.createElement("h1"));
setStyleName("rhw-heading");
}

public void setText (String s)
{
DOM.setInnerText(getElement(), s);
}


==== contents of Components.gwt.xml ====

<module>
<inherits name="'com.google.gwt.user.User'/">
</module>


NOTE: If you have your client code somewhere other than the "client" Java
package, you need to speficify that here. Same goes for the "public"
package. (See http://makeashorterlink.com/?G4332572D)

To use this JAR in your project you need to make a few changes:

1. Alter your *gwt.xml project file to reference the project file in the
JAR.

Example:

<module>
<!-- Inherit the core Web Toolkit stuff -->
<inherits name="'com.google.gwt.user.User'/">

<!-- ** ADD THIS ** -->
<inherits name="'org.hanson.gwt.Components'/">

<!-- Specify the app entry point class. -->
<entry-point
class="'org.hanson.gwt.client.MyApplication/">
</module>


2. Edit the MyApplication-shell.cmd and MyApplication-compile.cmd scripts,
add the jar to the classpath.

That should be it.

You should take a look at the gwt-user jar. You will find several project
files in there, including com.google.gwt.user.User. You will see that this
file references other projects, some of which use some currently
undocumented tags. Note that for each project in the gwt-user.jar file
there is a "client" package, and optionally a "local" package. You will see
that the images for the Tree widget are in one of the "local" packages.

Anyway, let me know if that helps.

29 comments:

Anonymous said...

Hi Robert. Thanks for putting together the GWT widget library. Your work in doing this is a great benefit to folks evaluating GWT like us. We though we'd contribute as well with our GWT Mortgage calculator example we put into production here:
http://www.cohomefinder.com/Colorado-mortgage.htm

We are documenting the effort and providing source here:
http://www.mooreds.com/weblog/

We are working on a version that doesn't require the expresso framework on the backend so it will be easier for folks to play with. Enjoy!

Anthony Francavilla
www.COhomefinder.com

Anonymous said...

The quotes in the XML tags are bit messed up, probably due to a cut and paste from the original thread.

THANKS FOR GWT PACKAGING SUMMARY!

Robert Hanson said...

I actually don't use Ant. For the GWT-WL I am currently using Maven 1.x, but plan to migrate to 2.x at some point.

Anonymous said...

Hi Robert. How about using external jars, e.g., user library functions that are imported from both client and server classes? Say, I have a gwtlib.jar containing Utilities.class for string manipulation. It doesn't have any UI widgets that need GWT compilation, no HTML, so I stumbled when I tried to package it as a GWT component, which makes sense I guess. Everything compiles fine in eclipse but running in hosted mode results in the good old "The import com.mysite cannot be resolved... Utilities cannot be resolved." The import works fine if used only on the server side. So I'm just duplicating everything into the client side for now which is, yes, dangerous and inconvenient. I think I'm missing some very basic concept here and would appreciate any pointers. Thanks! --J

Robert Hanson said...

RE: "The import com.mysite cannot be resolved... Utilities cannot be resolved."

You can definately do this. The only restriction is that the class MUST reside inside of the "client" package of a GWT module. Furthermore, if the class is inside of a module other than the GWT app, you must import it with an <import> tag in your GWT app's module.

In the example I gave in the article, I could have place the class in the package org.hanson.gwt.client, or any sub-package of that package, like org.hanson.gwt.client.utils.

So... it just sounds like you have the class in the wrong package. Move it into a GWT module, and all should be well.

Anonymous said...

Yikes! So it looks like I cannot reuse libraries from other apps/projects that do not use GWT because I cannot place them in the client folder :(

project1 (not GWT, packed in lib.jar)
--src
----com.mysite.lib
------Tools.java/class
------Utils.java/class
project2
--src
----com.mysite.mygwt
------public
------client
--------MyApp.java/class
------server

i.e., importing Utils.java in MyApp.java doesn't work, even if lib.jar is in build and source classpath for project2, correct?

I looked into the structure of gwt-user.jar as suggested and noticed the emul module. Would that be the way to go then, i.e., have an emul in com.mysite.mygwt to contain wrappers for classes in lib.jar and then do a super-source?

--J

Anonymous said...

I guess it's the same diff re emul, still have to import com.mysite.lib.Utils to wrap it into com.mysite.gwt.emul.com.mysite.lib.Utils, right?

--J

Robert Hanson said...

"I guess it's the same diff re emul, still have to import com.mysite.lib.Utils to wrap it into com.mysite.gwt.emul.com.mysite.lib.Utils, right?"

Yup.

Unknown said...

I think I'm really stupid, because I can't get your simple example running.

I created the jar, with the exact same content as you described. In the Test.java I added:

import org.hanson.gwt.client.Heading;

and in the onModuleLoad() method:

Heading h = new Heading();
h.setText("foo");

RootPanel.get().add(h);

I added to my .gwt.xml file of my project:

< inherits name='org.hanson.gwt.Components'/>

and changed the compile.cmd and shell.cmd:

-cp "%~dp0\src;
%~dp0\bin;
C:/gwt-windows-1.3.3/gwt-user.jar;
C:/gwt-windows-1.3.3/gwt-dev-windows.jar"

to

-cp "%~dp0\src;
%~dp0\bin;
C:/gwt-windows-1.3.3/gwt-user.jar;
C:/gwt-windows-1.3.3/gwt-dev-windows.jar;
C:/gwt-test/Heading.jar"

Still I get the error:

"The import org cannot be resolved" and "Heading cannot be resolved to a type"

What did I forgot?

Robert Hanson said...

Bugsfunny,

It sounds like you did everything right, so I am not sure what is causing the error. If it helps, feel free to send me the jar and your project, and I will take a look at it. My email is IamRobertHanson at gmail.com.

Hassan said...

Thanks Robert , :)

Anonymous said...

So if the source must be bundled in the jar it makes it impossible for third party libraries to have a distribution without revealing thier source. Has there been any thoughts on havign the GWT compilter work of obfuscated source since it cant work with the class files alone?

Robert Hanson said...

> Has there been any thoughts
> on having the GWT compiler
> work of obfuscated source

I haven't not heard of any plans to do so. I don't think that you will ever see this functionality in the core GWT compiler, but who knows.

I suppose that you could accomplish this by obfuscating the original Java source. I haven't looked, but there may be some tools to do this.

Frankly though, it is very easy to decompile Java class files, so even compiled files aren't actually hiding anything.

Anonymous said...

With Maven 2 you can add your Java files to your JAR by adding src/main/java as a resource directory as such:

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>

Anonymous said...

Very nice indeed. Great success with your code.

Unknown said...

Hi Robert.
I'm having a problem while adding a second component.
For both, I've followed your step by step, and it just worked for the first one.

This is the one that is not working:
MyComponent.jar
com/mycompany/component
And there I have a public and a client directories...
The code is in the client dir (HitList.java).

My MyComponent.gwt.xml is defined like this:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<module>

<inherits name='com.google.gwt.user.User'/>

<!-- I tried to remove this, as it is optional, but it didn't help -->
<source path="client"/>
</module>

Then I add the MyComponent.jar component to the application classpath and also to the Application-compile.cmd and Application-shell.cmd

Also, I've added one entry to Application.gwt.xml:

<inherits name="com.mycompany.component.MyComponent"/>

I'm getting this problem, when launching in Host Mode (or when trying to compile):

[ERROR] Line 3: The import com.mycompany cannot be resolved

[ERROR] Line 45: HitList cannot be resolved to a type

I'm using the GWT 1.5 milestone 1...
Do you have any clues on what should be going on??

Thanks a lot,
Michel.

Unknown said...

I'm posting this in case someone gets the same problem.

The problem was with the jar file itself...

Basically, I used Eclipse to create the jar, instead of creating that manually.
And then I've checked the "Add Directory Entries" when creating the jar, as it was said here:
http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/87f8c2dda3006760?tvc=2

Best regards,
Michel.

Anonymous said...
This comment has been removed by a blog administrator.
Debasish said...

Hi Robert,

Great post. One additional question though ...

How do we package the server package classes. I mean I have some reusable servlets that I would like to bundle in the jar and use in another web project. If this is possible how ?

thanks,
Debasish

Robert Hanson said...

Debasish, you can package the server components just like your normally would (compiled classes in a jar). Earlier versions of the GWT-WL did this (and still does come to think of it), where the jar contains both compiled classes and source side-by-side.

Debasish said...

Thanks Robert.

My servlets are working. But I never understood your point that GWT needs the source for client side code. Since my packages are working only with .class files for both client and server side though the only file I need in addition is gwt.xml files.

Robert Hanson said...

Debasish,

"But I never understood your point that GWT needs the source for client side code."

The GWT compiler does not operate on the compiled class files at all. It compiles the Java *source* files to JavaScript. This is why the source code needs to be in any jar file that you distribute.

One reason for this is that it allows the GWT compiler to make certain optimizations that it could not if it was looking at the class files. An example of this is generics, which is stripped by the Java compiler and not embedded in the generated class files. The GWT compiler needs this info to generate client-side RPC code.

There have been discussions about having the GWT compiler generate JS code from class files, but this just isn't possible. You can search the GWT Contrib forum for these conversations.

Debasish said...

Sorry to differ Robert.

My code is working that too amazingly well without any source code in the packaged jars.

Once I am ready with a release candidate I will update you first.

thanks for your help.

Unknown said...

Hi Robert,

i'm new to GWT and i was able to configure GWT with maven2. Trying to pass an entity to the RemoteServiceServlet i get this error:
[ERROR] Errors in 'file:/C:/Users/Nuno%20Silva/workspace/judoweb2/src/main/java/pt/uc/aac/judo/judoweb/gwt/presentation/gwt/client/TesteService.java'
[ERROR] Line 3: The import pt.uc.aac.judo.judoweb.atleta.entities.Atleta cannot be resolved
[ERROR] Line 12: Atleta cannot be resolved to a type

this means that it can´t find the Atleta class that is on other package.
How can i include this package/class in compile?

Thanks in advance
Nuno

Robert Hanson said...

Nuno,

Based on the error, and where it occurs, it looks like you are trying to use non-GWT-compatible code in your GWT app.

> [ERROR] Errors in 'file:/C:/Users/Nuno%20Silva/workspace/judoweb2/src/main/java/pt/uc/aac/judo/judoweb/gwt/presentation/gwt/client/TesteService.java'

I see "client" in the path, so the error is in a source file that is part of the GWT app, right?

> [ERROR] Line 3: The import pt.uc.aac.judo.judoweb.atleta.entities.Atleta cannot be resolved

I don't see "client" in the path, so my guess is that this is GWT compatible.

GWT apps can't just use any jar, they need to use code that is compatible (source is included in the jar, and all source uses the limited JRE, source is accompanied by a GWT module file that is imported by your app).

Unknown said...

Hi Robert,

Thanks for the response.

The application that i'm trying to integrate gwt is strutured in layers, (Persistence, Business, Presentation). The entity that i'm trying to access is transversal to all the layers so, so its on a different package.
What is the solution on this kind of issues, duplicating the entity, dosent seems to be the best solution.

Thanks.
Nuno

Robert Hanson said...

Nuno,

In this case it sounds like your only alternative is to use a DTO (duplicating the entity).

Some people use Dozer (http://dozer.sourceforge.net) for this, which makes it a bit easier. You might also want to pose the question on the GWT developers list, someone may have a more imaginative answer for you.

Unknown said...

Hi Robert,

I'm hoping you can help me with this. I'm basically
trying to make a web application using GWT. The code on the server end
though uses classes from external JAR files. I've added these JAR
files to the project (I'm using Eclipse). Still gives me a
NoClassDefFound exception. What else do i need to do ?

Anonymous said...

Thank you for adding:

"The widget class AND source must be in the "client" package"

Especially the AND!!! Thank you!!

Regards,
Steph