├── .circleci └── config.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.yml ├── LICENSE ├── README.md ├── example ├── App.vue ├── index.html ├── main.js └── webpack.config.js ├── package-lock.json ├── package.json ├── src ├── index.ts └── tsconfig.json ├── test ├── index.spec.ts └── tsconfig.json ├── tsconfig.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | defaults: &defaults 4 | working_directory: ~/working/repo 5 | docker: 6 | - image: circleci/node:12 7 | 8 | jobs: 9 | install: 10 | <<: *defaults 11 | steps: 12 | - checkout 13 | 14 | # Download and cache dependencies 15 | - restore_cache: 16 | keys: 17 | - v2-dependencies-{{ checksum "package-lock.json" }} 18 | # fallback to using the latest cache if no exact match is found 19 | - v2-dependencies- 20 | 21 | - run: npm install 22 | 23 | - save_cache: 24 | paths: 25 | - node_modules 26 | key: v2-dependencies-{{ checksum "package-lock.json" }} 27 | 28 | - persist_to_workspace: 29 | root: ~/working 30 | paths: 31 | - repo 32 | 33 | build: 34 | <<: *defaults 35 | steps: 36 | - attach_workspace: 37 | at: ~/working 38 | - run: npm run build 39 | 40 | test: 41 | <<: *defaults 42 | steps: 43 | - attach_workspace: 44 | at: ~/working 45 | - run: npm run test 46 | 47 | workflows: 48 | version: 2 49 | build_and_test: 50 | jobs: 51 | - install 52 | - build: 53 | requires: 54 | - install 55 | - test: 56 | requires: 57 | - install 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /lib/ 3 | /example/__build__.js -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /lib/ 2 | *.json -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 katashin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-media-loader 2 | 3 | Enable `media` attribute on Vue SFC styles. 4 | 5 | ## Requirements 6 | 7 | - vue-loader >= 15 8 | 9 | ## Installation 10 | 11 | ```sh 12 | $ npm i -D vue-media-loader 13 | ``` 14 | 15 | ## Usage 16 | 17 | Add `vue-loader` and `vue-media-loader` in your webpack config. Note that you need to insert `vue-media-loader` between any CSS preprocessors and `css-loader`. 18 | 19 | ```js 20 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 21 | 22 | module.exports = { 23 | // ... Other configs goes here ... 24 | 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.vue$/, 29 | use: 'vue-loader' 30 | }, 31 | { 32 | test: /\.scss$/, 33 | use: [ 34 | 'vue-style-loader', 35 | 'css-loader', 36 | 'vue-media-loader', 37 | 'sass-loader' 38 | ] 39 | } 40 | ] 41 | }, 42 | 43 | plugins: [new VueLoaderPlugin()] 44 | } 45 | ``` 46 | 47 | Then you can write `media` attribute on ` 59 | 60 | 65 | 66 | 71 | ``` 72 | 73 | The above code is equivalent with the following: 74 | 75 | ```vue 76 | 79 | 80 | 97 | ``` 98 | 99 | ## License 100 | 101 | MIT 102 | -------------------------------------------------------------------------------- /example/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-media-loader example 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | new Vue({ 5 | el: '#app', 6 | render: h => h(App) 7 | }) -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 3 | const base = path.resolve(__dirname) 4 | 5 | module.exports = { 6 | mode: 'development', 7 | context: base, 8 | entry: './main.js', 9 | output: { 10 | path: base, 11 | filename: '__build__.js' 12 | }, 13 | resolve: { 14 | alias: { 15 | vue$: 'vue/dist/vue.runtime.esm.js' 16 | } 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.vue$/, 22 | use: 'vue-loader' 23 | }, 24 | { 25 | test: /\.css$/, 26 | use: ['vue-style-loader', 'css-loader', path.resolve(base, '..')] 27 | } 28 | ] 29 | }, 30 | plugins: [new VueLoaderPlugin()] 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-media-loader", 3 | "version": "0.1.3", 4 | "author": "katashin", 5 | "description": "Enable `media` attribute on Vue SFC styles", 6 | "keywords": [ 7 | "Vue", 8 | "Single File Component", 9 | "webpack", 10 | "loader", 11 | "style", 12 | "css", 13 | "media", 14 | "media query" 15 | ], 16 | "license": "MIT", 17 | "main": "lib/index.js", 18 | "typings": "lib/index.d.ts", 19 | "files": [ 20 | "lib" 21 | ], 22 | "homepage": "https://github.com/ktsn/vue-media-loader", 23 | "bugs": "https://github.com/ktsn/vue-media-loader/issues", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/ktsn/vue-media-loader.git" 27 | }, 28 | "scripts": { 29 | "prepublishOnly": "npm run clean && npm run test && npm run build", 30 | "clean": "rm -rf lib", 31 | "build": "tsc -p src", 32 | "dev": "jest --watch", 33 | "example": "webpack --config example/webpack.config.js", 34 | "lint": "tslint -p . && prettier --list-different \"{src,scripts,test}/**/*.{js,ts}\"", 35 | "format": "prettier --write \"{src,scripts,test}/**/*.{js,ts}\"", 36 | "test": "npm run lint && npm run test:unit", 37 | "test:unit": "jest" 38 | }, 39 | "jest": { 40 | "transform": { 41 | "^.+\\.ts$": "ts-jest" 42 | }, 43 | "testRegex": "/test/.+\\.spec\\.(js|ts)$", 44 | "moduleFileExtensions": [ 45 | "ts", 46 | "js", 47 | "json" 48 | ], 49 | "globals": { 50 | "ts-jest": { 51 | "tsConfig": "test/tsconfig.json" 52 | } 53 | } 54 | }, 55 | "devDependencies": { 56 | "@types/jest": "^26.0.3", 57 | "@types/loader-utils": "^2.0.0", 58 | "@types/webpack": "^4.39.1", 59 | "css-loader": "^4.2.0", 60 | "jest": "^25.1.0", 61 | "prettier": "2.1.2", 62 | "ts-jest": "^25.0.0", 63 | "tslint": "^5.11.0", 64 | "tslint-config-ktsn": "^2.1.0", 65 | "tslint-config-prettier": "^1.15.0", 66 | "typescript": "^3.0.1", 67 | "vue": "^2.5.17", 68 | "vue-loader": "^15.4.1", 69 | "vue-style-loader": "^4.1.2", 70 | "vue-template-compiler": "^2.5.17", 71 | "webpack": "^4.17.1", 72 | "webpack-cli": "^3.1.0" 73 | }, 74 | "dependencies": { 75 | "loader-utils": "^2.0.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as loaderUtils from 'loader-utils' 2 | import { loader } from 'webpack' 3 | 4 | const vueMediaLoader: loader.Loader = function vueMediaLoader(code) { 5 | if (!this.resourceQuery) return code 6 | 7 | const options = loaderUtils.parseQuery(this.resourceQuery) 8 | 9 | // Ignore it if it does not come from Vue SFC or not have `media` attribute. 10 | if (!options.vue || !options.media) { 11 | return code 12 | } 13 | 14 | const str = String(code) 15 | const matched = str.match(/^\s*@charset\s*('.*'|".*")\s*;\s*/) 16 | const charset = matched ? matched[0] : '' 17 | const body = str.replace(charset, '') 18 | 19 | return `${charset}@media ${options.media} {\n${body}\n}` 20 | } 21 | export = vueMediaLoader 22 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../lib", 5 | "declaration": true 6 | }, 7 | "include": [ 8 | "**/*.ts" 9 | ] 10 | } -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as loader from '../src/index' 2 | 3 | function load(code: string, request: string): string { 4 | const [path, query] = request.split('?') 5 | 6 | return loader.call( 7 | { 8 | resourcePath: path, 9 | resourceQuery: query ? '?' + query : '', 10 | }, 11 | code 12 | ) 13 | } 14 | 15 | describe('vue-media-loader', () => { 16 | it('wraps the style with media query', () => { 17 | const code = '.foo { color: red; }' 18 | const media = '(max-width: 480px)' 19 | const res = load(code, './Test.vue?vue&media=' + encodeURIComponent(media)) 20 | expect(res).toBe(`@media ${media} {\n${code}\n}`) 21 | }) 22 | 23 | it('does nothing if media query is not specified', () => { 24 | const code = '.foo { color: red; }' 25 | const res = load(code, './Test.vue?vue') 26 | expect(res).toBe(code) 27 | }) 28 | 29 | it('does nothing if not come from Vue SFC', () => { 30 | const code = '.foo { color: red; }' 31 | const res = load( 32 | code, 33 | './Test.vue?media=' + encodeURIComponent('(max-width: 480px)') 34 | ) 35 | expect(res).toBe(code) 36 | }) 37 | 38 | it('extracts charset at-rule', () => { 39 | const code = '@charset "UTF-8";\n.foo { color: red; }' 40 | const media = 'print' 41 | const res = load(code, './Test.vue?vue&media=' + encodeURIComponent(media)) 42 | expect(res).toBe( 43 | `@charset "UTF-8";\n@media ${media} {\n.foo { color: red; }\n}` 44 | ) 45 | }) 46 | 47 | it('not throws when query is not passed', () => { 48 | const code = '.foo { color: red; }' 49 | const res = load(code, './Test.vue') 50 | expect(res).toBe(code) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "lib": [ 8 | "es2015" 9 | ], 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true 13 | }, 14 | "include": [ 15 | "src/**/*.ts", 16 | "test/**/*.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-ktsn", 4 | "tslint-config-prettier" 5 | ] 6 | } 7 | --------------------------------------------------------------------------------