You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SassLinker.java 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.sass.linker;
  17. import java.io.ByteArrayInputStream;
  18. import java.io.File;
  19. import java.io.FileOutputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.util.ArrayList;
  24. import java.util.Date;
  25. import java.util.List;
  26. import org.w3c.css.sac.CSSException;
  27. import com.google.gwt.core.ext.LinkerContext;
  28. import com.google.gwt.core.ext.TreeLogger;
  29. import com.google.gwt.core.ext.UnableToCompleteException;
  30. import com.google.gwt.core.ext.linker.AbstractLinker;
  31. import com.google.gwt.core.ext.linker.ArtifactSet;
  32. import com.google.gwt.core.ext.linker.EmittedArtifact;
  33. import com.google.gwt.core.ext.linker.LinkerOrder;
  34. import com.google.gwt.core.ext.linker.LinkerOrder.Order;
  35. import com.google.gwt.core.ext.linker.Shardable;
  36. import com.vaadin.sass.internal.ScssStylesheet;
  37. /**
  38. * Pre-linker that checks for the existence of SASS files in public folders,
  39. * compiles them to CSS files with the SassCompiler from Vaadin and adds the CSS
  40. * back into the artifact.
  41. *
  42. */
  43. @LinkerOrder(Order.PRE)
  44. @Shardable
  45. public class SassLinker extends AbstractLinker {
  46. @Override
  47. public String getDescription() {
  48. return "Compiling SCSS files in public folders to standard CSS";
  49. }
  50. @Override
  51. public ArtifactSet link(TreeLogger logger, LinkerContext context,
  52. ArtifactSet artifacts, boolean onePermutation)
  53. throws UnableToCompleteException {
  54. if (!onePermutation) {
  55. // The artifact to return
  56. ArtifactSet toReturn = new ArtifactSet(artifacts);
  57. // The temporary scss files provided from the artefacts
  58. List<FileInfo> scssFiles = new ArrayList<>();
  59. // The public files are provided as inputstream, but the compiler
  60. // needs real files, as they can contain references to other
  61. // files. They will be stored here, with their relative paths intact
  62. String tempFolderName = new Date().getTime() + File.separator;
  63. File tempFolder = createTempDir(tempFolderName);
  64. // Can't search here specifically for public resources, as the type
  65. // is different during compilation. This means we have to loop
  66. // through all the artifacts
  67. for (EmittedArtifact resource : artifacts
  68. .find(EmittedArtifact.class)) {
  69. // Create the temporary files.
  70. String partialPath = resource.getPartialPath();
  71. if (partialPath.endsWith(".scss")) {
  72. // In my opinion, the SCSS file does not need to be
  73. // output to the web content folder, as they can't
  74. // be used there
  75. toReturn.remove(resource);
  76. String fileName = partialPath;
  77. File path = tempFolder;
  78. int separatorIndex = fileName.lastIndexOf(File.separator);
  79. if (-1 != separatorIndex) {
  80. fileName = fileName.substring(separatorIndex + 1);
  81. String filePath = partialPath.substring(0,
  82. separatorIndex);
  83. path = createTempDir(tempFolderName + filePath);
  84. }
  85. File tempfile = new File(path, fileName);
  86. try {
  87. boolean fileCreated = tempfile.createNewFile();
  88. if (fileCreated) {
  89. // write the received inputstream to the temp file
  90. writeFromInputStream(resource.getContents(logger),
  91. tempfile);
  92. // Store the file info for the compilation
  93. scssFiles.add(new FileInfo(tempfile, partialPath));
  94. } else {
  95. logger.log(TreeLogger.WARN,
  96. "Duplicate file " + tempfile.getPath());
  97. }
  98. } catch (IOException e) {
  99. logger.log(TreeLogger.ERROR,
  100. "Could not write temporary file " + fileName,
  101. e);
  102. }
  103. }
  104. }
  105. // Compile the files and store them in the artifact
  106. logger.log(TreeLogger.INFO,
  107. "Processing " + scssFiles.size() + " Sass file(s)");
  108. for (FileInfo fileInfo : scssFiles) {
  109. logger.log(TreeLogger.INFO, " " + fileInfo.originalScssPath
  110. + " -> " + fileInfo.getOriginalCssPath());
  111. try {
  112. ScssStylesheet scss = ScssStylesheet
  113. .get(fileInfo.getAbsolutePath());
  114. if (!fileInfo.isMixin()) {
  115. scss.compile();
  116. InputStream is = new ByteArrayInputStream(
  117. scss.printState().getBytes());
  118. toReturn.add(this.emitInputStream(logger, is,
  119. fileInfo.getOriginalCssPath()));
  120. }
  121. fileInfo.getFile().delete();
  122. } catch (CSSException e) {
  123. logger.log(TreeLogger.ERROR, "SCSS compilation failed for "
  124. + fileInfo.getOriginalCssPath(), e);
  125. } catch (IOException e) {
  126. logger.log(TreeLogger.ERROR, "Could not write CSS file for "
  127. + fileInfo.getOriginalCssPath(), e);
  128. } catch (Exception e) {
  129. logger.log(TreeLogger.ERROR, "SCSS compilation failed for "
  130. + fileInfo.getOriginalCssPath(), e);
  131. }
  132. }
  133. return toReturn;
  134. }
  135. return artifacts;
  136. }
  137. /**
  138. * Writes the contents of an InputStream out to a file.
  139. *
  140. * @param contents
  141. * @param tempfile
  142. * @throws IOException
  143. */
  144. private void writeFromInputStream(InputStream contents, File tempfile)
  145. throws IOException {
  146. // write the inputStream to a FileOutputStream
  147. OutputStream out = new FileOutputStream(tempfile);
  148. int read = 0;
  149. byte[] bytes = new byte[1024];
  150. while ((read = contents.read(bytes)) != -1) {
  151. out.write(bytes, 0, read);
  152. }
  153. contents.close();
  154. out.flush();
  155. out.close();
  156. }
  157. /**
  158. * Create folder in temporary space on disk.
  159. *
  160. * @param partialPath
  161. * @return
  162. */
  163. private File createTempDir(String partialPath) {
  164. String baseTempPath = System.getProperty("java.io.tmpdir");
  165. File tempDir = new File(baseTempPath + File.separator + partialPath);
  166. if (!tempDir.exists()) {
  167. tempDir.mkdirs();
  168. }
  169. tempDir.deleteOnExit();
  170. return tempDir;
  171. }
  172. /**
  173. * Temporal storage for file info from Artifact.
  174. */
  175. private class FileInfo {
  176. private String originalScssPath;
  177. private File file;
  178. public FileInfo(File file, String originalScssPath) {
  179. this.file = file;
  180. this.originalScssPath = originalScssPath;
  181. }
  182. public boolean isMixin() {
  183. return file.getName().startsWith("_");
  184. }
  185. public String getAbsolutePath() {
  186. return file.getAbsolutePath();
  187. }
  188. public String getOriginalCssPath() {
  189. return originalScssPath.substring(0, originalScssPath.length() - 5)
  190. + ".css";
  191. }
  192. public File getFile() {
  193. return file;
  194. }
  195. }
  196. }