JSIncludeScript, JSIncludeCSS, and JSIncludeAsset metadata

These are similar to the -js-include-script, -js-include-css, and -js-include-asset compiler options, except that they associate these files with the specific package-level definition. So, if they are compiled into a SWC, the files are copied, and <script>/<link> tags are added, only if that definition is actually used. The compiler options do that if any definition in a SWC is used.

Works similarly to the classic [Embed] metadata with a source attribute to specify a file path that is either absolute or relative to the current .as or .mxml source file.
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/COMPJSCNative.java b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/COMPJSCNative.java
index 6837c0d..b8998ca 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/COMPJSCNative.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/COMPJSCNative.java
@@ -28,6 +28,7 @@
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.nio.file.Files;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.ZoneId;
@@ -56,6 +57,9 @@
 import org.apache.royale.compiler.clients.MXMLJSC.JSTargetType;
 import org.apache.royale.compiler.clients.problems.ProblemQuery;
 import org.apache.royale.compiler.codegen.js.IJSWriter;
+import org.apache.royale.compiler.definitions.IDefinition;
+import org.apache.royale.compiler.definitions.metadata.IMetaTag;
+import org.apache.royale.compiler.definitions.metadata.IMetaTagAttribute;
 import org.apache.royale.compiler.driver.IBackend;
 import org.apache.royale.compiler.driver.js.IJSApplication;
 import org.apache.royale.compiler.exceptions.ConfigurationException;
@@ -65,12 +69,14 @@
 import org.apache.royale.compiler.internal.parsing.as.RoyaleASDocDelegate;
 import org.apache.royale.compiler.internal.projects.CompilerProject;
 import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
+import org.apache.royale.compiler.internal.scopes.ASProjectScope.DefinitionPromise;
 import org.apache.royale.compiler.internal.targets.RoyaleSWCTarget;
 import org.apache.royale.compiler.internal.units.SWCCompilationUnit;
 import org.apache.royale.compiler.internal.watcher.WatchThread;
 import org.apache.royale.compiler.internal.watcher.WatchThread.IWatchWriter;
 import org.apache.royale.compiler.internal.targets.JSTarget;
 import org.apache.royale.compiler.internal.workspaces.Workspace;
+import org.apache.royale.compiler.problems.FileNotFoundProblem;
 import org.apache.royale.compiler.problems.ICompilerProblem;
 import org.apache.royale.compiler.problems.InternalCompilerProblem;
 import org.apache.royale.compiler.problems.LibraryNotFoundProblem;
@@ -420,6 +426,9 @@
                         processSourceMap(sourceMapTemp, baos, outputClassFile, symbol);
                         writeFileToZip(zipOutputStream, sourceMapFilePath, baos, fileList);
                     }
+
+                    writeJSIncludesForCompilationUnitToZip(cu, zipOutputStream, fileList);
+
                     writer.close();
                 }
             }
@@ -502,6 +511,125 @@
         return true;
     }
 
+    private void writeJSIncludesForCompilationUnitToZip(ICompilationUnit cu, ZipOutputStream zipOutputStream, StringBuilder fileList) throws IOException
+    {
+        for (IDefinition def : cu.getDefinitionPromises())
+        {
+            if (def instanceof DefinitionPromise)
+            {
+                def = ((DefinitionPromise) def).getActualDefinition();
+            }
+            for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeScript"))
+            {
+                for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                {
+                    String key = metaAttr.getKey();
+                    if ("source".equals(key) || key == null)
+                    {
+                        String includePath = metaAttr.getValue();
+                        
+                        File includedFile = new File(includePath);
+                        if (!includedFile.isAbsolute())
+                        {
+                            File basePath = new File(def.getContainingFilePath()).getParentFile();
+                            includedFile = new File(basePath, includePath);   
+                        }
+
+                        if (includedFile.exists() && !includedFile.isDirectory())
+                        {
+                            String includedFilePath = "js/scripts-meta/" + includedFile.getName();
+                            if (config.isVerbose())
+                            {
+                                System.out.println("Writing file: " + includedFilePath);
+                            }
+                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                            byte[] includedFileBytes = Files.readAllBytes(includedFile.toPath());
+                            baos.write(includedFileBytes);
+                            writeFileToZip(zipOutputStream, includedFilePath, baos, fileList);
+                        }
+                        else
+                        {
+                            problems.add(new FileNotFoundProblem(metaTag, includePath));
+                        }
+                        break;
+                    }
+                }
+            }
+            for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeCSS"))
+            {
+                for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                {
+                    String key = metaAttr.getKey();
+                    if ("source".equals(key) || key == null)
+                    {
+                        String includePath = metaAttr.getValue();
+                        
+                        File includedFile = new File(includePath);
+                        if (!includedFile.isAbsolute())
+                        {
+                            File basePath = new File(def.getContainingFilePath()).getParentFile();
+                            includedFile = new File(basePath, includePath);   
+                        }
+
+                        if (includedFile.exists() && !includedFile.isDirectory())
+                        {
+                            String includedFilePath = "js/css-meta/" + includedFile.getName();
+                            if (config.isVerbose())
+                            {
+                                System.out.println("Writing file: " + includedFilePath);
+                            }
+                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                            byte[] includedFileBytes = Files.readAllBytes(includedFile.toPath());
+                            baos.write(includedFileBytes);
+                            writeFileToZip(zipOutputStream, includedFilePath, baos, fileList);
+                        }
+                        else
+                        {
+                            problems.add(new FileNotFoundProblem(metaTag, includePath));
+                        }
+                        break;
+                    }
+                }
+            }
+            for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeAsset"))
+            {
+                for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                {
+                    String key = metaAttr.getKey();
+                    if ("source".equals(key) || key == null)
+                    {
+                        String includePath = metaAttr.getValue();
+                        
+                        File includedFile = new File(includePath);
+                        if (!includedFile.isAbsolute())
+                        {
+                            File basePath = new File(def.getContainingFilePath()).getParentFile();
+                            includedFile = new File(basePath, includePath);   
+                        }
+
+                        if (includedFile.exists() && !includedFile.isDirectory())
+                        {
+                            String includedFilePath = "js/assets-meta/" + includedFile.getName();
+                            if (config.isVerbose())
+                            {
+                                System.out.println("Writing file: " + includedFilePath);
+                            }
+                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                            byte[] includedFileBytes = Files.readAllBytes(includedFile.toPath());
+                            baos.write(includedFileBytes);
+                            writeFileToZip(zipOutputStream, includedFilePath, baos, fileList);
+                        }
+                        else
+                        {
+                            problems.add(new FileNotFoundProblem(metaTag, includePath));
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
     private void processSourceMap(ByteArrayOutputStream sourceMapTemp, ByteArrayOutputStream baos, File outputClassFile, String symbol)
     {
         String sourceMapSourceRoot = project.config.getSourceMapSourceRoot();
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/COMPJSCRoyale.java b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/COMPJSCRoyale.java
index b1550cd..989aa8c 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/COMPJSCRoyale.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/COMPJSCRoyale.java
@@ -28,6 +28,7 @@
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.nio.file.Files;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.ZoneId;
@@ -48,6 +49,9 @@
 import org.apache.royale.compiler.clients.MXMLJSC.JSTargetType;
 import org.apache.royale.compiler.clients.problems.ProblemQuery;
 import org.apache.royale.compiler.codegen.js.IJSWriter;
+import org.apache.royale.compiler.definitions.IDefinition;
+import org.apache.royale.compiler.definitions.metadata.IMetaTag;
+import org.apache.royale.compiler.definitions.metadata.IMetaTagAttribute;
 import org.apache.royale.compiler.driver.IBackend;
 import org.apache.royale.compiler.driver.js.IJSApplication;
 import org.apache.royale.compiler.exceptions.ConfigurationException;
@@ -57,12 +61,14 @@
 import org.apache.royale.compiler.internal.parsing.as.RoyaleASDocDelegate;
 import org.apache.royale.compiler.internal.projects.CompilerProject;
 import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
+import org.apache.royale.compiler.internal.scopes.ASProjectScope.DefinitionPromise;
 import org.apache.royale.compiler.internal.targets.RoyaleSWCTarget;
 import org.apache.royale.compiler.internal.units.SWCCompilationUnit;
 import org.apache.royale.compiler.internal.watcher.WatchThread;
 import org.apache.royale.compiler.internal.watcher.WatchThread.IWatchWriter;
 import org.apache.royale.compiler.internal.targets.JSTarget;
 import org.apache.royale.compiler.internal.workspaces.Workspace;
+import org.apache.royale.compiler.problems.FileNotFoundProblem;
 import org.apache.royale.compiler.problems.ICompilerProblem;
 import org.apache.royale.compiler.problems.InternalCompilerProblem;
 import org.apache.royale.compiler.problems.LibraryNotFoundProblem;
@@ -442,6 +448,9 @@
                         processSourceMap(sourceMapTemp, baos, outputClassFile, symbol);
                         writeFileToZip(zipOutputStream, sourceMapFilePath, baos, fileList);
                     }
+
+                    writeJSIncludesForCompilationUnitToZip(cu, zipOutputStream, fileList);
+
                     writer.close();
                 }
             }
@@ -569,6 +578,125 @@
         return true;
     }
 
+    private void writeJSIncludesForCompilationUnitToZip(ICompilationUnit cu, ZipOutputStream zipOutputStream, StringBuilder fileList) throws IOException
+    {
+        for (IDefinition def : cu.getDefinitionPromises())
+        {
+            if (def instanceof DefinitionPromise)
+            {
+                def = ((DefinitionPromise) def).getActualDefinition();
+            }
+            for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeScript"))
+            {
+                for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                {
+                    String key = metaAttr.getKey();
+                    if ("source".equals(key) || key == null)
+                    {
+                        String includePath = metaAttr.getValue();
+                        
+                        File includedFile = new File(includePath);
+                        if (!includedFile.isAbsolute())
+                        {
+                            File basePath = new File(def.getContainingFilePath()).getParentFile();
+                            includedFile = new File(basePath, includePath);   
+                        }
+
+                        if (includedFile.exists() && !includedFile.isDirectory())
+                        {
+                            String includedFilePath = "js/scripts-meta/" + includedFile.getName();
+                            if (config.isVerbose())
+                            {
+                                System.out.println("Writing file: " + includedFilePath);
+                            }
+                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                            byte[] includedFileBytes = Files.readAllBytes(includedFile.toPath());
+                            baos.write(includedFileBytes);
+                            writeFileToZip(zipOutputStream, includedFilePath, baos, fileList);
+                        }
+                        else
+                        {
+                            problems.add(new FileNotFoundProblem(metaTag, includePath));
+                        }
+                        break;
+                    }
+                }
+            }
+            for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeCSS"))
+            {
+                for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                {
+                    String key = metaAttr.getKey();
+                    if ("source".equals(key) || key == null)
+                    {
+                        String includePath = metaAttr.getValue();
+                        
+                        File includedFile = new File(includePath);
+                        if (!includedFile.isAbsolute())
+                        {
+                            File basePath = new File(def.getContainingFilePath()).getParentFile();
+                            includedFile = new File(basePath, includePath);   
+                        }
+
+                        if (includedFile.exists() && !includedFile.isDirectory())
+                        {
+                            String includedFilePath = "js/css-meta/" + includedFile.getName();
+                            if (config.isVerbose())
+                            {
+                                System.out.println("Writing file: " + includedFilePath);
+                            }
+                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                            byte[] includedFileBytes = Files.readAllBytes(includedFile.toPath());
+                            baos.write(includedFileBytes);
+                            writeFileToZip(zipOutputStream, includedFilePath, baos, fileList);
+                        }
+                        else
+                        {
+                            problems.add(new FileNotFoundProblem(metaTag, includePath));
+                        }
+                        break;
+                    }
+                }
+            }
+            for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeAsset"))
+            {
+                for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                {
+                    String key = metaAttr.getKey();
+                    if ("source".equals(key) || key == null)
+                    {
+                        String includePath = metaAttr.getValue();
+                        
+                        File includedFile = new File(includePath);
+                        if (!includedFile.isAbsolute())
+                        {
+                            File basePath = new File(def.getContainingFilePath()).getParentFile();
+                            includedFile = new File(basePath, includePath);   
+                        }
+
+                        if (includedFile.exists() && !includedFile.isDirectory())
+                        {
+                            String includedFilePath = "js/assets-meta/" + includedFile.getName();
+                            if (config.isVerbose())
+                            {
+                                System.out.println("Writing file: " + includedFilePath);
+                            }
+                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                            byte[] includedFileBytes = Files.readAllBytes(includedFile.toPath());
+                            baos.write(includedFileBytes);
+                            writeFileToZip(zipOutputStream, includedFilePath, baos, fileList);
+                        }
+                        else
+                        {
+                            problems.add(new FileNotFoundProblem(metaTag, includePath));
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
     private void processSourceMap(ByteArrayOutputStream sourceMapTemp, ByteArrayOutputStream baos, File outputClassFile, String symbol)
     {
         String sourceMapSourceRoot = project.config.getSourceMapSourceRoot();
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/mxml/royale/MXMLRoyalePublisher.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/mxml/royale/MXMLRoyalePublisher.java
index 5e03d9c..76a388b 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/mxml/royale/MXMLRoyalePublisher.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/mxml/royale/MXMLRoyalePublisher.java
@@ -30,11 +30,13 @@
 import org.apache.commons.io.filefilter.RegexFileFilter;
 import org.apache.royale.compiler.clients.problems.ProblemQuery;
 import org.apache.royale.compiler.codegen.js.royale.IJSRoyalePublisher;
+import org.apache.royale.compiler.common.ISourceLocation;
 import org.apache.royale.compiler.config.Configuration;
 import org.apache.royale.compiler.css.ICSSPropertyValue;
 import org.apache.royale.compiler.definitions.IClassDefinition;
 import org.apache.royale.compiler.definitions.IDefinition;
 import org.apache.royale.compiler.definitions.metadata.IMetaTag;
+import org.apache.royale.compiler.definitions.metadata.IMetaTagAttribute;
 import org.apache.royale.compiler.filespecs.IFileSpecification;
 import org.apache.royale.compiler.internal.codegen.js.JSPublisher;
 import org.apache.royale.compiler.internal.codegen.js.goog.JarSourceFile;
@@ -48,8 +50,11 @@
 import org.apache.royale.compiler.internal.scopes.ASProjectScope.DefinitionPromise;
 import org.apache.royale.compiler.internal.targets.ITargetAttributes;
 import org.apache.royale.compiler.internal.units.SWCCompilationUnit;
+import org.apache.royale.compiler.problems.FileInLibraryNotFoundProblem;
 import org.apache.royale.compiler.problems.FileNotFoundProblem;
 import org.apache.royale.compiler.problems.HTMLTemplateFileNotFoundProblem;
+import org.apache.royale.compiler.problems.JSIncludeMetaTagNoSourceAttributeProblem;
+import org.apache.royale.compiler.problems.JSIncludeMetaTagUnknownAttributeProblem;
 import org.apache.royale.compiler.units.ICompilationUnit;
 import org.apache.royale.compiler.utils.JSClosureCompilerWrapper;
 import org.apache.royale.swc.ISWC;
@@ -980,6 +985,11 @@
     private void copyIncludeFileFromSwcToOutput(String type, ISWC swc, String swcFileKey, String fileOutputPath, ProblemQuery problems)
     {
         ISWCFileEntry swcFileEntry = swc.getFile(swcFileKey);
+        if (swcFileEntry == null)
+        {
+            problems.add(new FileInLibraryNotFoundProblem(swcFileKey, swc.getSWCFile().getAbsolutePath()));
+            return;
+        }
         try
         {
             InputStream is = swcFileEntry.createInputStream();
@@ -1007,32 +1017,88 @@
         }
     }
 
-    private void copyIncludeFileToOutput(String type, String originFilePath, String targetOutputPath, ProblemQuery problems)
+    private void handleJSIncludeAssetFromSWC(String type, String swcFileEntryKey, ISWC swc, StringBuilder depsHTML, ProblemQuery problems)
     {
-        File scriptFile = new File(originFilePath);
-        if (scriptFile.exists() && !scriptFile.isDirectory())
+        String assetOutputPath = Paths.get("assets").resolve(Paths.get(swcFileEntryKey).getFileName()).toString();
+        copyIncludeFileFromSwcToOutput(type, swc, swcFileEntryKey, assetOutputPath, problems);
+    }
+
+    private void handleJSIncludeAsset(String type, File includedAssetFile, StringBuilder depsHTML, ISourceLocation sourceLocation, ProblemQuery problems)
+    {
+        String assetOutputPath = Paths.get("assets").resolve(includedAssetFile.getName()).toString();
+        copyIncludeFileToOutput(type, includedAssetFile, assetOutputPath, null, problems);
+    }
+
+    private void handleJSIncludeScriptFromSWC(String type, String swcFileEntryKey, ISWC swc, StringBuilder depsHTML, ProblemQuery problems)
+    {
+        String scriptOutputPath = Paths.get("scripts").resolve(Paths.get(swcFileEntryKey).getFileName()).toString();
+        depsHTML.append("\t<script type=\"text/javascript\" src=\"");
+        depsHTML.append(scriptOutputPath);
+        depsHTML.append("\"></script>\n");
+
+        copyIncludeFileFromSwcToOutput(type, swc, swcFileEntryKey, scriptOutputPath, problems);
+    }
+
+    private void handleJSIncludeScript(String type, File includedScriptFile, StringBuilder depsHTML, ISourceLocation sourceLocation, ProblemQuery problems)
+    {
+        String scriptOutputPath = Paths.get("scripts").resolve(includedScriptFile.getName()).toString();
+        depsHTML.append("\t<script type=\"text/javascript\" src=\"");
+        depsHTML.append(scriptOutputPath);
+        depsHTML.append("\"></script>\n");
+
+        copyIncludeFileToOutput(type, includedScriptFile, scriptOutputPath, null, problems);
+    }
+
+    private void handleJSIncludeCSSFromSWC(String type, String swcFileEntryKey, ISWC swc, StringBuilder depsHTML, ProblemQuery problems)
+    {
+        String cssOutputPath = Paths.get("css").resolve(Paths.get(swcFileEntryKey).getFileName()).toString();
+
+        depsHTML.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\"");
+        depsHTML.append(cssOutputPath);
+        depsHTML.append("\">\n");
+
+        copyIncludeFileFromSwcToOutput(type, swc, swcFileEntryKey, cssOutputPath, problems);
+    }
+
+    private void handleJSIncludeCSS(String type, File includedCSSFile, StringBuilder depsHTML, ISourceLocation sourceLocation, ProblemQuery problems)
+    {
+        String cssOutputPath = Paths.get("css").resolve(includedCSSFile.getName()).toString();
+        depsHTML.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\"");
+        depsHTML.append(cssOutputPath);
+        depsHTML.append("\">\n");
+
+        copyIncludeFileToOutput(type, includedCSSFile, cssOutputPath, sourceLocation, problems);
+    }
+
+    private void copyIncludeFileToOutput(String type, File originFile, String targetOutputPath, ISourceLocation sourceLocation, ProblemQuery problems)
+    {
+        if (originFile.exists() && !originFile.isDirectory())
         {
             try
             {
                 if ("intermediate".equals(type))
                 {
                     final File intermediateDir = outputFolder;
-                    FileUtils.copyFile(scriptFile, new File(intermediateDir, targetOutputPath));
+                    FileUtils.copyFile(originFile, new File(intermediateDir, targetOutputPath));
                 }
                 else
                 {
                     final File releaseDir = new File(outputParentFolder, ROYALE_RELEASE_DIR_NAME);
-                    FileUtils.copyFile(scriptFile, new File(releaseDir, targetOutputPath));
+                    FileUtils.copyFile(originFile, new File(releaseDir, targetOutputPath));
                 }
             }
             catch (IOException e)
             {
-                throw new RuntimeException("Unable to copy script file: " + scriptFile.getAbsolutePath());
+                throw new RuntimeException("Unable to copy script file: " + originFile.getAbsolutePath());
             }
         }
+        else if (sourceLocation != null)
+        {
+            problems.add(new FileNotFoundProblem(sourceLocation, originFile.getPath()));
+        }
         else
         {
-            problems.add(new FileNotFoundProblem(originFilePath));
+            problems.add(new FileNotFoundProblem(originFile.getPath()));
         }
     }
 
@@ -1049,7 +1115,161 @@
         List<ICompilationUnit> reachableUnits = project.getReachableCompilationUnitsInSWFOrder(Arrays.asList(project.mainCU));
         for (ICompilationUnit unit : reachableUnits)
         {
-            if (!(unit instanceof SWCCompilationUnit))
+            boolean isSWCUnit = unit instanceof SWCCompilationUnit;
+            for (IDefinition def : unit.getDefinitionPromises())
+            {
+                if (def instanceof DefinitionPromise)
+                {
+                    def = ((DefinitionPromise) def).getActualDefinition();
+                    for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeScript"))
+                    {
+                        boolean foundSource = false;
+                        for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                        {
+                            String key = metaAttr.getKey();
+                            if ("source".equals(key) || key == null)
+                            {
+                                foundSource = true;
+
+                                String includePath = metaAttr.getValue();
+                                if (isSWCUnit)
+                                {
+                                    SWCCompilationUnit swcUnit = (SWCCompilationUnit) unit;
+                                    ISWC swc = swcUnit.getSWC();
+                                    String fileName = new File(includePath).getName();
+                                    String swcFileEntryPath = "js\\scripts-meta\\" + fileName;
+                                    ISWCFileEntry swcFileEntry = swc.getFile(swcFileEntryPath);
+                                    if (swcFileEntry == null)
+                                    {
+                                        swcFileEntryPath = "js/scripts-meta/" + fileName;
+                                        swcFileEntry = swc.getFile(swcFileEntryPath);
+                                    }
+                                    handleJSIncludeScriptFromSWC(type, swcFileEntryPath, swc, depsHTML, problems);
+                                }
+                                else
+                                {
+                                    File includedFile = new File(includePath);
+                                    if (!includedFile.isAbsolute())
+                                    {
+                                        File basePath = new File(def.getContainingFilePath()).getParentFile();
+                                        includedFile = new File(basePath, includePath);   
+                                    }
+
+                                    handleJSIncludeScript(type, includedFile, depsHTML, null, problems);
+                                }
+                                break;
+                            }
+                            else
+                            {
+                                problems.add(new JSIncludeMetaTagUnknownAttributeProblem(metaTag, key));
+                            }
+                        }
+                        if (!foundSource)
+                        {
+                            problems.add(new JSIncludeMetaTagNoSourceAttributeProblem(metaTag));
+                        }
+                    }
+                    for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeCSS"))
+                    {
+                        boolean foundSource = false;
+                        for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                        {
+                            String key = metaAttr.getKey();
+                            if ("source".equals(key) || key == null)
+                            {
+                                foundSource = true;
+
+                                String includePath = metaAttr.getValue();
+                                
+                                if (isSWCUnit)
+                                {
+                                    SWCCompilationUnit swcUnit = (SWCCompilationUnit) unit;
+                                    ISWC swc = swcUnit.getSWC();
+                                    String fileName = new File(includePath).getName();
+                                    String swcFileEntryPath = "js\\css-meta\\" + fileName;
+                                    ISWCFileEntry swcFileEntry = swc.getFile(swcFileEntryPath);
+                                    if (swcFileEntry == null)
+                                    {
+                                        swcFileEntryPath = "js/css-meta/" + fileName;
+                                        swcFileEntry = swc.getFile(swcFileEntryPath);
+                                    }
+                                    handleJSIncludeCSSFromSWC(type, swcFileEntryPath, swc, depsHTML, problems);
+                                }
+                                else
+                                {
+                                    File includedFile = new File(includePath);
+                                    if (!includedFile.isAbsolute())
+                                    {
+                                        File basePath = new File(def.getContainingFilePath()).getParentFile();
+                                        includedFile = new File(basePath, includePath);   
+                                    }
+
+                                    handleJSIncludeCSS(type, includedFile, depsHTML, metaTag, problems);
+                                }
+                            }
+                            else
+                            {
+                                problems.add(new JSIncludeMetaTagUnknownAttributeProblem(metaTag, key));
+                            }
+                        }
+                        if (!foundSource)
+                        {
+                            problems.add(new JSIncludeMetaTagNoSourceAttributeProblem(metaTag));
+                        }
+                    }
+                    for (IMetaTag metaTag : def.getMetaTagsByName("JSIncludeAsset"))
+                    {
+                        boolean foundSource = false;
+                        for (IMetaTagAttribute metaAttr : metaTag.getAllAttributes())
+                        {
+                            String key = metaAttr.getKey();
+                            if ("source".equals(key) || key == null)
+                            {
+                                foundSource = true;
+
+                                String includePath = metaAttr.getValue();
+                                
+                                if (isSWCUnit)
+                                {
+                                    SWCCompilationUnit swcUnit = (SWCCompilationUnit) unit;
+                                    ISWC swc = swcUnit.getSWC();
+                                    String fileName = new File(includePath).getName();
+                                    String swcFileEntryPath = "js\\assets-meta\\" + fileName;
+                                    ISWCFileEntry swcFileEntry = swc.getFile(swcFileEntryPath);
+                                    if (swcFileEntry == null)
+                                    {
+                                        swcFileEntryPath = "js/assets-meta/" + fileName;
+                                        swcFileEntry = swc.getFile(swcFileEntryPath);
+                                    }
+                                    handleJSIncludeAssetFromSWC(type, swcFileEntryPath, swc, depsHTML, problems);
+                                }
+                                else
+                                {
+                                    File includedFile = new File(includePath);
+                                    if (!includedFile.isAbsolute())
+                                    {
+                                        File basePath = new File(def.getContainingFilePath()).getParentFile();
+                                        includedFile = new File(basePath, includePath);   
+                                    }
+
+                                    handleJSIncludeAsset(type, includedFile, depsHTML, metaTag, problems);
+                                }
+                                break;
+                            }
+                            else
+                            {
+                                problems.add(new JSIncludeMetaTagUnknownAttributeProblem(metaTag, key));
+                            }
+                        }
+                        if (!foundSource)
+                        {
+                            problems.add(new JSIncludeMetaTagNoSourceAttributeProblem(metaTag));
+                        }
+                    }
+                }
+            }
+
+            if (!isSWCUnit)
             {
                 continue;
             }
@@ -1076,17 +1296,15 @@
         {
             for (String key : swc.getFiles().keySet())
             {
-                if (key.startsWith("js/assets") || key.startsWith("js\\assets"))
+                if (key.startsWith("js/assets/") || key.startsWith("js\\assets\\"))
                 {
-                    String assetPath = Paths.get("js").relativize(Paths.get(key)).toString();
-                    copyIncludeFileFromSwcToOutput(type, swc, key, assetPath, problems);
+                    handleJSIncludeAssetFromSWC(type, key, swc, depsHTML, problems);
                 }
             }
         }
         for (String asset : googConfiguration.getJSIncludeAsset())
         {
-            String assetOutputPath = Paths.get("assets").resolve(Paths.get(asset).getFileName()).toString();
-            copyIncludeFileToOutput(type, asset, assetOutputPath, problems);
+            handleJSIncludeAsset(type, new File(asset), depsHTML, null, problems);
         }
 
         // included CSS appears before included JS scripts
@@ -1095,27 +1313,16 @@
         {
             for (String key : swc.getFiles().keySet())
             {
-                if (key.startsWith("js/css") || key.startsWith("js\\css"))
+                if (key.startsWith("js/css/") || key.startsWith("js\\css\\"))
                 {
-                    String cssPath = Paths.get("js").relativize(Paths.get(key)).toString();
-
-                    depsHTML.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\"");
-                    depsHTML.append(cssPath);
-                    depsHTML.append("\">\n");
-
-                    copyIncludeFileFromSwcToOutput(type, swc, key, cssPath, problems);
+                    handleJSIncludeCSSFromSWC(type, key, swc, depsHTML, problems);
                 }
             }
         }
 
         for (String css : googConfiguration.getJSIncludeCss())
         {
-            String cssOutputPath = Paths.get("css").resolve(Paths.get(css).getFileName()).toString();
-            depsHTML.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\"");
-            depsHTML.append(cssOutputPath);
-            depsHTML.append("\">\n");
-
-            copyIncludeFileToOutput(type, css, cssOutputPath, problems);
+            handleJSIncludeCSS(type, new File(css), depsHTML, null, problems);
         }
 
         // included JS scripts appear after included CSS
@@ -1125,26 +1332,16 @@
         {
             for (String key : swc.getFiles().keySet())
             {
-                if (key.startsWith("js/scripts") || key.startsWith("js\\scripts"))
+                if (key.startsWith("js/scripts/") || key.startsWith("js\\scripts\\"))
                 {
-                    String scriptPath = Paths.get("js").relativize(Paths.get(key)).toString();
-                    depsHTML.append("\t<script type=\"text/javascript\" src=\"");
-                    depsHTML.append(scriptPath);
-                    depsHTML.append("\"></script>\n");
-
-                    copyIncludeFileFromSwcToOutput(type, swc, key, scriptPath, problems);
+                    handleJSIncludeScriptFromSWC(type, key, swc, depsHTML, problems);
                 }
             }
         }
 
         for (String script : googConfiguration.getJSIncludeScript())
         {
-            String scriptOutputPath = Paths.get("scripts").resolve(Paths.get(script).getFileName()).toString();
-            depsHTML.append("\t<script type=\"text/javascript\" src=\"");
-            depsHTML.append(scriptOutputPath);
-            depsHTML.append("\"></script>\n");
-
-            copyIncludeFileToOutput(type, script, scriptOutputPath, problems);
+            handleJSIncludeScript(type, new File(script), depsHTML, null, problems);
         }
 
         if ("intermediate".equals(type))
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/problems/JSIncludeMetaTagNoSourceAttributeProblem.java b/compiler-jx/src/main/java/org/apache/royale/compiler/problems/JSIncludeMetaTagNoSourceAttributeProblem.java
new file mode 100644
index 0000000..0bad7b5
--- /dev/null
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/problems/JSIncludeMetaTagNoSourceAttributeProblem.java
@@ -0,0 +1,41 @@
+/*
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.royale.compiler.problems;
+
+import org.apache.royale.compiler.definitions.metadata.IMetaTag;
+
+public final class JSIncludeMetaTagNoSourceAttributeProblem extends CompilerProblem
+{
+    public static final String DESCRIPTION =
+        "${metaTagName} requires a '${SOURCE}' file attribute";
+
+    public static final int errorCode = 1346;
+    
+    public JSIncludeMetaTagNoSourceAttributeProblem(IMetaTag site)
+    {
+        super(site);
+        metaTagName = site.getTagName();
+    }
+    
+    // Prevent these from being localized.
+    public final String SOURCE = "source";
+
+    public String metaTagName;
+}
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/problems/JSIncludeMetaTagUnknownAttributeProblem.java b/compiler-jx/src/main/java/org/apache/royale/compiler/problems/JSIncludeMetaTagUnknownAttributeProblem.java
new file mode 100644
index 0000000..1a04781
--- /dev/null
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/problems/JSIncludeMetaTagUnknownAttributeProblem.java
@@ -0,0 +1,40 @@
+/*
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.royale.compiler.problems;
+
+import org.apache.royale.compiler.definitions.metadata.IMetaTag;
+
+public final class JSIncludeMetaTagUnknownAttributeProblem extends CompilerProblem
+{
+    public static final String DESCRIPTION =
+        "Unknown attribute on ${metaTagName} meta tag: ${attr}";
+
+    public static final int errorCode = 1358;
+    public JSIncludeMetaTagUnknownAttributeProblem(IMetaTag site, String attr)
+    {
+        super(site);
+        metaTagName = site.getTagName();
+        this.attr = attr;
+    }
+
+    public final String attr;
+
+    public String metaTagName;
+}
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/css/CSSManager.java b/compiler/src/main/java/org/apache/royale/compiler/internal/css/CSSManager.java
index 1e61179..6e3fae7 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/css/CSSManager.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/css/CSSManager.java
@@ -284,9 +284,12 @@
                     	if ("css".equalsIgnoreCase(suffix)
                                 && !fileName.contains("default")
                                 // ignore .css files bundled with -js-include-css
-                                // because they are simply added as <link> tags in HTML
+                                // or with [JSIncludeCSS] because they are
+                                // separately added as <link> tags in HTML
                                 && !fileName.startsWith("js/css/")
-                                && !fileName.startsWith("js\\css\\"))
+                                && !fileName.startsWith("js\\css\\")
+                                && !fileName.startsWith("js/css-meta/")
+                                && !fileName.startsWith("js\\css-meta\\"))
                     	{
                             final CacheStoreKeyBase key = CSSDocumentCache.createKey(swc, fileName);
                             final ICSSDocument extracss = cssCache.get(key);