diff --git a/deps/weex-styler/index.js b/deps/weex-styler/index.js index b4ea0ff9e6156a6b73183c8031a7756c98dd91a5..02e5aef23d6401d42ffa15238353c5458b5e7d52 100644 --- a/deps/weex-styler/index.js +++ b/deps/weex-styler/index.js @@ -3,12 +3,9 @@ var css = require('css') var util = require('./lib/util') var validateItem = require('./lib/validator').validate -var fs = require('fs') -var path = require('path') var SELECTOR_MATCHER = /^[\.#]?[A-Za-z0-9_\-:]+$/ var DESCENDANT_SELECTOR_MATCHER = /^([.#]?[A-Za-z0-9_-]+(\s+|\s*>\s*))+([.#]?[A-Za-z0-9_\-:]+)$/ -var IMPORT_MATCHER = /(['"]([^()]+?)['"])|(['"]([^()]+?)['"]\s+(only|not)?\s?(screen)?\s?((and|or|,|not|landscape)?\s?[(]([^()])+[)]\s*)+)/g var LENGTH_REGEXP = /^[-+]?\d*\.?\d+(\S*)$/ const CARD_SELECTOR = /^[\.#][A-Za-z0-9_\-]+$/ const card = process.env.DEVICE_LEVEL === 'card' @@ -341,9 +338,6 @@ function parse(code, done, resourcePath) { jsonStyle['@FONT-FACE'].push(ruleResult) } } - else if (type === 'import') { - parseImport(resourcePath, rule, jsonStyle, log) - } else if (type === 'keyframes' && !card) { if (!jsonStyle['@KEYFRAMES']) { jsonStyle['@KEYFRAMES'] = {} @@ -442,9 +436,6 @@ function parse(code, done, resourcePath) { if (rule.rules && rule.rules.length) { rule.rules.forEach(function(rule) { ruleResult = {} - if (rule.type === 'import') { - parseImport(resourcePath, rule, mediaObj, log) - } if (rule.declarations && rule.declarations.length) { flexExpand(rule, ruleLog) rule.declarations.forEach(function (declaration) { @@ -526,45 +517,6 @@ function parse(code, done, resourcePath) { done(err, {jsonStyle: jsonStyle, log: log}) } -function parseImport(resourcePath, rule, jsonStyle, log) { - if(!resourcePath) { - return - } - let importString = rule.import - let importPath - let mediaString = '' - let source = '' - if (importString.match(IMPORT_MATCHER)) { - let filePath = importString.match(/['"]([^()]+?)['"]/) - importPath = filePath[1] - mediaString = importString.replace(importPath, '').replace(/['"]/g, '') - } - if(/^(\.)|(\.\.)\//.test(importPath)) { - resourcePath = resourcePath.substring(0, resourcePath.lastIndexOf(path.sep) + 1); - importPath = path.resolve(resourcePath, importPath) - } - if (fs.existsSync(importPath)) { - source = fs.readFileSync(importPath).toString() - } else { - log.push({ - line: rule.position.start.line, - column: rule.position.start.column, - reason: 'ERROR: no such file or directory, open ' + importPath - }) - return - } - if (mediaString.length !== 0) { - source = '@media ' + mediaString + '{\n' + source + '\n}' - } - parse(source, (err, obj) => { - if (err) { - throw(err) - } else { - jsonStyle = Object.assign(jsonStyle, obj.jsonStyle) - } - }, importPath) -} - /** * Validate a JSON Object and log errors & warnings * diff --git a/src/loader.js b/src/loader.js index 9908af162a94f80111a9345f9d049f22450edee3..1595efc57a7e9749d9d5bea3c58ef21c33179c87 100644 --- a/src/loader.js +++ b/src/loader.js @@ -20,6 +20,9 @@ import loaderUtils from 'loader-utils' import path from 'path' import fs from 'fs' +import css from 'css' +import sass from 'sass' +import less from 'less' import * as legacy from './legacy' import { @@ -49,6 +52,10 @@ const defaultLoaders = { manifest: path.resolve(loaderPath, 'manifest-loader.js'), resourceReferenceScript: path.resolve(loaderPath, 'resource-reference-script.js') } +const cssImportPaths = new Set(); +const mediaImportPaths = []; +const IMPORT_MATCHER = + /(['"]([^()]+?)['"])|(['"]([^()]+?)['"]\s+(only|not)?\s?(screen)?\s?((and|or|,|not|landscape)?\s?[(]([^()])+[)]\s*)+)/g function getLoaderString (type, config) { config = config || {} @@ -337,73 +344,236 @@ function loadPageCheckElementLength (_this, elementLength, frag, elementNames, r } function loadPageFindCss (_this, filename, customLang) { - let output = '' - let extcss = false - const cssFileName = filename + '.css' + let output = ''; + let extcss = false; + let cssFileName = filename + '.css'; if (fs.existsSync(cssFileName)) { - extcss = true - output = 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', { - customLang, - lang: undefined, - element: undefined, - elementName: undefined, - source: cssFileName - }), cssFileName) - } - else { - // find less - const lessFileName = filename + '.less' - if (fs.existsSync(lessFileName)) { - extcss = true - output = 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', { - customLang, - lang: 'less', - element: undefined, - elementName: undefined, - source: lessFileName - }), lessFileName) - } - else { - // find scss - const scssFileName = filename + '.scss' - if (fs.existsSync(scssFileName)) { + extcss = true; + } else { + cssFileName = filename + '.less'; + if (fs.existsSync(cssFileName)) { + extcss = true; + } else { + cssFileName = filename + '.scss' + if (fs.existsSync(cssFileName)) { extcss = true - output = 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', { - customLang, - lang: 'scss', - element: undefined, - elementName: undefined, - source: scssFileName - }), scssFileName) - } - else { - // find sass - const sassFileName = filename + '.sass' - if (fs.existsSync(sassFileName)) { + } else { + cssFileName = filename + '.sass' + if (fs.existsSync(cssFileName)) { extcss = true - output = 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', { - customLang, - lang: 'sass', - element: undefined, - elementName: undefined, - source: sassFileName - }), sassFileName) - } - else { + } else { extcss = false } } } } + if (extcss) { + collectStyleImports(_this, cssFileName); + output = getOutputWithImportStyle(_this, cssFileName, customLang); + } return { extcss: extcss, output: output } } +function collectStyleImports(_this, fileName, condition) { + switch (getStyleType(path.extname(fileName))) { + case 'less': + parseLess(fileName).then(css => { + parseCss(_this, css, fileName, condition); + }).catch(e => { + logWarn(_this, [{ + reason: `ERROR: Failed to parse the less file. ${fileName}\n` + e + }]); + }); + break; + case "sass": + const cssInfoFromSass = parseSass(fileName); + if (cssInfoFromSass) { + parseCss(_this, cssInfoFromSass, fileName, condition); + } + break; + case "scss": + const cssInfoFromScss = parseSass(fileName); + if (cssInfoFromScss) { + parseCss(_this, cssInfoFromScss, fileName, condition); + } + break; + default: + parseCss(_this, fs.readFileSync(fileName), fileName, condition); + break; + } +} + +function parseSass(fileName) { + const sassInfo = sass.renderSync({file: fileName}); + if (sassInfo.css) { + return sassInfo.css; + } +} + +function parseLess(fileName) { + return new Promise((resolve, reject) => { + less.render(fs.readFileSync(fileName, 'utf-8'), + function(err, obj){ + if (err) { + reject(err); + } else { + resolve(obj.css); + } + } + )}); +} + +function parseCss(_this, code, resourcePath, condition) { + let parseCode = typeof code === 'string' ? code : code.toString(); + const ast = css.parse(parseCode, { silent: true}); + if (!ast) { + return; + } + const parsingErrors = ast.stylesheet.parsingErrors; + if (parsingErrors && parsingErrors.length) { + parsingErrors.forEach(function (error) { + logWarn(_this, [{ + line: error.line, + column: error.column, + reason: error.toString().replace('Error', 'ERROR') + }]); + }); + } + if (ast.type === 'stylesheet' && ast.stylesheet && ast.stylesheet.rules && + ast.stylesheet.rules.length) { + ast.stylesheet.rules.forEach(function(rule) { + const type = rule.type; + if (type === 'import') { + parseImport(_this, resourcePath, rule, condition); + } else if (type === 'media') { + var mediaCondition = rule.media + if (rule.rules && rule.rules.length) { + rule.rules.forEach(function(rule) { + if (rule.type === 'import') { + parseImport(_this, resourcePath, rule, mediaCondition); + } + }) + } + } + }); + } +} + +function parseImport(_this, resourcePath, rule, condition) { + if (!resourcePath) { + return; + } + let importString = rule.import; + let importPath; + let mediaString = ''; + let source = ''; + if (importString.match(IMPORT_MATCHER)) { + let filePath = importString.match(/['"]([^()]+?)['"]/); + importPath = filePath[1]; + mediaString = importString.replace(importPath, '').replace(/['"]/g, ''); + } + if(/^(\.)|(\.\.)\//.test(importPath)) { + resourcePath = resourcePath.substring(0, resourcePath.lastIndexOf(path.sep) + 1); + importPath = path.resolve(resourcePath, importPath); + } + if (fs.existsSync(importPath)) { + source = fs.readFileSync(importPath).toString(); + if (mediaString && mediaString.length) { + mediaImportPaths.push({ + condition: mediaString, + path: importPath + }); + } else if (condition && condition.length){ + mediaImportPaths.push({ + condition: condition, + path: importPath + }); + } else { + cssImportPaths.add(importPath); + } + } else { + logWarn(_this, [{ + line: rule.position.start.line, + column: rule.position.start.column, + reason: 'ERROR: no such file or directory, open ' + importPath + }]); + return; + } + if (mediaString && mediaString.length) { + source = '@media ' + mediaString + '{\n' + source + '\n}'; + collectStyleImports(_this, importPath, mediaString); + } else if (condition && condition.length){ + collectStyleImports(_this, importPath, condition); + } else { + collectStyleImports(_this, importPath); + } +} + +function getOutputWithImportStyle(_this, filename, customLang) { + let output = ''; + const langType = getStyleType(path.extname(filename)); + output = 'var $app_style$ = Object.assign(' + + getRequireString(_this, getLoaderString('style', { + customLang, + lang: langType, + element: undefined, + elementName: undefined, + source: filename + }), filename).replace('\n', ''); + cssImportPaths.forEach(importPath => { + output += ',' + getRequireString(_this, getLoaderString('style', { + customLang, + lang: getStyleType(path.extname(importPath)), + element: undefined, + elementName: undefined, + source: importPath + }), importPath).replace('\n', ''); + }) + let mediaString = `, JSON.parse(\'{"@MEDIA": [\'`; + mediaImportPaths.forEach(media => { + if (mediaString === `, JSON.parse(\'{"@MEDIA": [\'`) { + mediaString += ` + \'{"condition":"${media.condition}", \' + JSON.stringify(` + + getRequireString(_this, getLoaderString('style', { + customLang, + lang: getStyleType(path.extname(media.path)), + element: undefined, + elementName: undefined, + source: media.path + }), media.path).replace('\n', '') + ') + \'}\''; + } else { + mediaString += ` + \',{"condition":"${media.condition}", \' + JSON.stringify(` + + getRequireString(_this, getLoaderString('style', { + customLang, + lang: getStyleType(path.extname(media.path)), + element: undefined, + elementName: undefined, + source: media.path + }), media.path).replace('\n', '') + ') + \'}\''; + } + }) + mediaString += '+\']}\')'; + output += mediaString; + output += ')\n'; + return output; +} + +function getStyleType(extName) { + switch (extName) { + case '.less': + return 'less'; + case ".sass": + return 'sass'; + case ".scss": + return 'scss'; + } +} + function loadPageFindJs (_this, filename, customLang) { let output = '' - let extscript = false + let extscript = false; const jsFileName = filename + '.js' if (!fs.existsSync(jsFileName)) { extscript = false