├── .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 |
77 | This text color will be changed if you change the window width.
78 |
79 |
80 |
97 | ```
98 |
99 | ## License
100 |
101 | MIT
102 |
--------------------------------------------------------------------------------
/example/App.vue:
--------------------------------------------------------------------------------
1 |
2 | This text color will be changed if you change the window width.
3 |
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 |
--------------------------------------------------------------------------------