下載安裝javascript
git clone https://github.com/mandyshen97/vue-todo.git
運行css
npm run dev
目的:html
webpack
+vue
目標:前端
"vue": "^2.6.10", "vue-loader": "^15.7.0", "vue-template-compiler": "^2.6.10", "webpack": "^4.32.2", "webpack-dev-server": "^3.5.1", "webpack-cli": "^3.3.2"
"dependencies": { "autoprefixer": "^9.5.1", "babel-core": "^6.26.3", "babel-loader": "^7.1.5", "babel-plugin-syntax-jsx": "^6.18.0", "babel-plugin-transform-vue-jsx": "^3.7.0", "babel-preset-env": "^1.7.0", "cross-env": "^5.2.0", "css-loader": "^2.1.1", "extract-text-webpack-plugin": "^4.0.0-beta.0", "file-loader": "^3.0.1", "html-webpack-plugin": "^3.2.0", "postcss-loader": "^3.0.0", "style-loader": "^0.23.1", "stylus": "^0.54.5", "stylus-loader": "^3.0.2", "url-loader": "^1.1.2", "vue": "^2.6.10", "vue-loader": "^15.7.0", "vue-template-compiler": "^2.6.10", "webpack": "^4.32.2", "webpack-dev-server": "^3.5.1" }, "devDependencies": { "webpack-cli": "^3.3.2" }
vue+webpack
項目工程配置webpack
項目npm init
生成 package.json
vue
npm install webpack vue vue-loader
npm i css-loader vue-template-compiler
此時項目就初始化好了。java
webpack
項目配置src
,文件夾src/assets
,文件src/App.vue
,入口文件src/index.js
在App.vue
中寫以下簡單組件代碼:node
// App.vue <template> <div id="test">{{text}}</div> </template> <script> export default { data() { return { text: 'abcd' } } } </script> <style> #test { color: red; } </style>
顯然這個組件是沒法在瀏覽器中直接運行的,下面的操做使它能夠運行。webpack
index.js
中將App
組件掛載到dom
節點中。// index.js /** * 入口文件 */ import Vue from 'vue'; import App from './App.vue'; const root = document.createElement('div'); document.body.appendChild(root); // 建立Vue對象,將App組件掛載到root節點 new Vue({ render: (h) => h(App) }).$mount(root)
webpack
是幫咱們打包前端資源的,咱們的前端資源有不少類型,好比說javascript
,css
,images
,字體等,這些都是要經過http
請求去加載的內容。
package.json
同級位置創建webpack.config.js
文件。// webpack.config.js // path是nodejs中的一個基本包,用來處理路徑的 const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); module.exports = { // 聲明入口,entry使用絕對路徑,保證不出錯誤 entry: path.join(__dirname, 'src/index.js'), mode: 'production', // 出口 output: { // 輸出打包文件名(將index.js以及其依賴的資源打包成bundle.js) filename: 'bundle.js', // 輸出路徑 path: path.join(__dirname, 'dist') }, module: { rules: [ { test: /.vue$/, loader: 'vue-loader' }, { test: /.css$/, loader:['css-loader'] } ] }, plugins: [ new VueLoaderPlugin() ] }
package.json
中添加build
:只有在這裏調用這個webpack
,纔會調用咱們安裝在項目裏面的webpack
,若是不在這裏添加,直接在命令行裏面輸,則使用的是全局的webpack
,版本可能不同,應該在這裏添加。
// package.json "scripts": { "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack --config webpack.config.js" },
npm run build
此時在項目文件夾下生成dist
文件夾及dist/bundle.js
git
loader
配置webpack.config.js
中添加下列內容:module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(gif|jpg|jpeg|png|svg)$/, use: [ { loader: 'url-loader', options: { limit: 1024, name: '[name]-aaa.[ext]' } } ] } ] }
安裝上面對應的loader
github
npm i style-loader url-loader file-loader
assets
文件夾中建立assets/images
,assets/styles
styles
下建立test.css
body{ color: red; background-image: url('../images/do.jpg'); }
index.js
中import
這個test.css
和圖片文件// index.js import './assets/styles/test.css'; import './assets/images/bg.jpg';
npm run build
能夠看到圖片被打包到了dist文件夾下
。bundle.js
中也有了test.css
的內容.
webpack.config.js
的module模塊的rules中添加css預處理器的規則。{ test: /\.styl/, use: [ 'style-loader', 'css-loader', 'stylus-loader' ] }
npm i stylus-loader stylus
styles
目錄下新建test-stylus.styl
文件// test-stylus.styl body font-size 20px
index.js
中引入test-stylus.styl
import './assets/styles/test-stylus.styl';
npm run build
此時項目的目錄結構以下:
webpack-dev-server
webpack-dev-server
是一個webpack
的包,功能很是強大,能夠去webpack
官網https://webpack.docschina.org...查看詳細配置。首先安裝:npm i webpack-dev-server
package.json
中添加"dev""scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config webpack.config.js", + "dev": "webpack-dev-server --config webpack.config.js" },
修改webpack.config.js
的配置來適應webpack-dev-server
的開發模式。
module.exports = { // 編譯目標是web平臺 + target: 'web',
由於在不一樣的平臺上設置環境變量的方式是不同的,使用cross-env
來在不一樣的環境下使用一樣的腳本。
npm i cross-env
修改package.json
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "webpack --config webpack.config.js", + "build": "cross-env NODE_ENV=production webpack --config webpack.config.js", - "dev": "webpack-dev-server --config webpack.config.js", + "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js" },
在webpack.config.js
中判斷:
// webpack.config.js // path是nodejs中的一個基本包,用來處理路徑的 const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); - module.exports = { + const isDev = process.env.NODE_ENV === 'development'; + const config = { ... } + if(isDev){ + config.devServer = { // 端口 port: 8080, // 主機 host: '0.0.0.0', // 使webpack錯誤顯示到頁面上 overlay: { error: true, } } }
html
文件使項目在瀏覽器中能打開。html
插件npm i html-webpack-plugin
配置插件:
+ const HTMLPlugin = require('html-webpack-plugin'); plugins: [ + new webpack.DefinePlugin({ 'process.env': { NODE_ENV: isDev ? '"development"' : '"production"' } }), new VueLoaderPlugin(), + new HTMLPlugin() ]
運行:
npm run dev
在瀏覽器中訪問:http://127.0.0.1:8080/
或http://localhost:8080/
此時改變頁面內容,保存,瀏覽器的顯示能夠自動刷新!!
devtool
// 若是是開發模式,則進行下列配置 if(isDev){ // 控制是否生成,以及如何生成 source map + config.devtool = '#cheap-module-eval-source-map'; config.devServer = { // 端口 port: 8080, // 主機 host: '0.0.0.0', // 使webpack錯誤顯示到頁面上 overlay: { error: true, }, // 自動打開瀏覽器 // open: true, // 模塊熱替換,只更新更改的部分 + hot: true }; // 添加插件 + config.plugins.push( // 熱模塊替換插件 new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ); }
npm run dev
改變內容,頁面局部刷新。
此時的文件內容:
// webpack.config.js // path是nodejs中的一個基本包,用來處理路徑的 const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const HTMLPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const isDev = process.env.NODE_ENV === 'development'; const config = { // 編譯目標是web平臺 target: 'web', // 聲明入口,entry使用絕對路徑,保證不出錯誤 entry: path.join(__dirname, 'src/index.js'), mode: 'production', // 出口 output: { // 輸出打包文件名(將index.js以及其依賴的資源打包成bundle.js) filename: 'bundle.js', // 輸出路徑 path: path.join(__dirname, 'dist') }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(gif|jpg|jpeg|png|svg)$/, use: [ { loader: 'url-loader', options: { limit: 1024, name: '[name]-aaa.[ext]' } } ] }, { test: /\.styl/, use: [ 'style-loader', 'css-loader', 'stylus-loader' ] } ] }, plugins: [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: isDev ? '"development"' : '"production"' } }), new VueLoaderPlugin(), new HTMLPlugin() ] } // 若是是開發模式,則進行下列配置 if(isDev){ // 控制是否生成,以及如何生成 source map config.devtool = '#cheap-module-eval-source-map'; config.devServer = { // 端口 port: 8080, // 主機 host: '0.0.0.0', // 使webpack錯誤顯示到頁面上 overlay: { error: true, }, // 自動打開瀏覽器 // open: true, // 模塊熱替換,只更新更改的部分 hot: true }; // 添加插件 config.plugins.push( // 熱模塊替換插件 new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ); } module.exports = config;
此時的文件內容:
// package.json { "name": "vue-todo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "cross-env NODE_ENV=production webpack --config webpack.config.js", "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js" }, "author": "", "license": "ISC", "dependencies": { "cross-env": "^5.2.0", "css-loader": "^2.1.1", "file-loader": "^3.0.1", "html-webpack-plugin": "^3.2.0", "style-loader": "^0.23.1", "stylus": "^0.54.5", "stylus-loader": "^3.0.2", "url-loader": "^1.1.2", "vue": "^2.6.10", "vue-loader": "^15.7.0", "vue-template-compiler": "^2.6.10", "webpack": "^4.32.2", "webpack-dev-server": "^3.5.1" }, "devDependencies": { "webpack-cli": "^3.3.2" } }
到此時爲止,項目配置基本完成,以後寫業務邏輯。
vue
介紹和項目實戰vue是一個數據綁定的組件化的框架
數據綁定
vue文件開發方式
render方法
API重點:
vue
的jsx
寫法以及postcss
npm i postcss-loader autoprefixer babel-loader babel-cor
.babelrc
,postcss.config.js
npm i babel-preset-env babel-plugin-transform-vue-jsx
// postcss.config.js const autoprefixer = require('autoprefixer'); module.exports = { plugins: [ autoprefixer() ] };
// .babelrc { "presets": [ "env" ], "plugins": [ "transform-vue-jsx" ] }
webpack.config.js
中配置{ test: /\.jsx$/, loader: 'babel-loader' },
todo
應用界面在src
目錄下新建src/components
目錄,新建Header.vue
,Todo.vue
,Item.vue
,Tabs.vue
,Footer.jsx
這些組件和global.styl
。
目錄結構以下:
// index.js /** * 入口文件 */ import Vue from 'vue'; import App from './App.vue'; import '../src/assets/styles/global.styl'; const root = document.createElement('div'); document.body.appendChild(root); // 建立Vue對象,將App組件掛載到root節點 new Vue({ render: (h) => h(App) }).$mount(root);
<!-- Header.vue--> <template> <header class="main-header"> <h1>ToDo</h1> </header> </template> <style lang="stylus" scoped> .main-header { text-align: center; h1 { font-size: 100px; color: palevioletred; font-weight: 100px; margin: 50px; } } </style>
<!--Todo.vue--> <template> <section class="real-app"> <input type="text" class="add-input" autofocus="autofocus" placeholder="添加任務" @keyup.enter="addTodo" > <Item :todo="todo" v-for="todo in filteredTodos" :key="todo.id" @del="deleteTodo" /> <Tabs :filter="filter" :todos="todos" @toggle="toggleFilter" @clearAllCompleted="clearAllCompleted" /> </section> </template> <script> import Item from './Item.vue'; import Tabs from './Tabs.vue'; let id = 0; export default { data(){ return { todos: [], filter: "all" } }, components: { Item, Tabs }, computed: { filteredTodos(){ // 若是 filter的狀態爲all,顯示全部的todos if(this.filter === 'all'){ return this.todos; } // 篩選的方法就是讓this.filter等於todo.completed, // 可是filter是一個字符串,因此加下面這個條件判斷, // 獲得一個true或false的值,用它去過濾todos列表 const completed = this.filter === 'completed'; // filter的結果返回true則顯示,返回false則不顯示 return this.todos.filter(todo => completed === todo.completed) } }, methods: { addTodo(e) { this.todos.unshift({ id: id++, content: e.target.value.trim(), completed: false }); e.target.value = ''; }, deleteTodo(id){ this.todos.splice(this.todos.findIndex(todo => todo.id === id), 1); }, toggleFilter(state){ this.filter = state; }, clearAllCompleted(){ this.todos = this.todos.filter(todo => !todo.completed); } } } </script> <style scoped> .real-app { width: 600px; margin: 0 auto; box-shadow: 0 0 5px #666; } .add-input { position: relative; width: 100%; min-height: 40px; padding-left: 60px; line-height: 40px; font-size: 16px; border: 3px solid pink; box-sizing: border-box; background-color: ghostwhite; } </style>
<!--Item.vue--> <template> <div :class="['todo-item', todo.completed ? 'completed' : '']"> <input type="checkbox" id="toggle" v-model="todo.completed" > <label>{{todo.content}}</label> <button class="del" @click="deleteTodo"></button> </div> </template> <script> export default { props: { todo: { type: Object, required: true, } }, methods: { deleteTodo() { // 觸發事件,在父組件中用@del="deleteTodo"的方式監聽, // 實現父子組件間事件的解耦 this.$emit('del', this.todo.id); } } } </script> <style lang="stylus" scoped> .todo-item { position: relative; background-color: white; font-size: 24px; border-bottom: 1px solid pink; &:hover { .del:after { content: 'x'; } } label { white-space: pre-line; word-break: break-all; padding: 15px 60px 15px 15px; margin-left: 45px; display: block; line-height: 1.2; transition: color 0.4s; } &.completed { label { color: #d9d9d9; text-decoration: line-through; } } } #toggle { position: absolute; text-align: center; width: 20px; height: 20px; left: 10px; top: 0; bottom: 0; margin: auto 0; border: none; outline: none; } .del { position: absolute; top: 0; bottom: 0; right: 10px; width: 40px; height: 40px; margin: auto 0; color: red; background-color: transparent; font-size: 30px; border-width: 0; cursor: pointer; outline: none; } </style>
<!--Tabs.vue--> <template> <div class="helper"> <span class="left">{{unFinishedTodoLength}} items left</span> <span class="tabs"> <span v-for="state in states" :key="state" :class="[state, filter === state ? 'actived' : '']" @click="toggleFilter(state)" > {{state}} </span> </span> <span class="clear" @click="clearAllCompleted">Clear completed</span> </div> </template> <script> export default { props: { filter: { type: String, required: true, }, todos: { type: Array, required: true } }, data() { return { states: ['all', 'active', 'completed'] } }, computed: { unFinishedTodoLength() { return this.todos.filter(todo => !todo.completed).length; } }, methods: { toggleFilter(state) { this.$emit('toggle', state); }, clearAllCompleted() { this.$emit('clearAllCompleted'); } } } </script> <style lang="stylus" scoped> .helper { font-family: Georgia, serif; font-weight: 100; display: flex; justify-content: space-between; padding: 5px 0; line-height: 30px; background-color: #fff; font-size: 14px; } .left, .clear, .tabs { padding: 0 10px; box-sizing: border-box; } .left, .clear { width: 150px; } .left { text-align: left; } .clear { text-align: right; cursor: pointer; } .tabs { width: 200px; display: flex; justify-content: space-around; * { display: inline-block; padding 0 10px; cursor: pointer; border: 1px solid rgba(175, 47, 47, 0); &.actived { border-color: rgba(175, 47, 47, 0.4); border-radius: 5px; } } } </style>
<!--App.vue--> <template> <div id="app"> <div id="cover"></div> <Header/> <Todo/> <Footer/> </div> </template> <script> import Header from './components/Header.vue'; import Footer from './components/Footer.jsx'; import Todo from './components/Todo.vue'; export default { components: { Header, Footer, Todo } } </script> <style scoped> #app { position: absolute; left: 0; right: 0; top: 0; bottom: 0; } /* 實現虛化效果*/ #cover { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background-color: #999; opacity: .6; z-index: -1; } </style>
// Footer.jsx import '../assets/styles/footer.styl'; export default { data() { return { author: 'MandyShen' } }, render() { return ( <div id="footer"> <span>Written by {this.author}</span> </div> ) } };
//footer.styl #footer{ margin-top 40px text-align center color mediumvioletred font-size 18px text-shadow 0 1px 0 #2b81af }
webpack
配置優化webpack
配置css
單獨分離打包npm i extract-text-webpack-plugin
做用是將非javascript
代碼的資源單獨打包成一個靜態資源文件。
webpack.config.js
中引入// webpack.config.js const ExteactPlugin = require('extract-text-webpack-plugin');
webpack
區分打包類庫代碼及hash
優化// webpack.config.js // path是nodejs中的一個基本包,用來處理路徑的 const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const HTMLPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const ExtractPlugin = require('extract-text-webpack-plugin'); const isDev = process.env.NODE_ENV === 'development'; const config = { // 編譯目標是web平臺 target: 'web', // 聲明入口,entry使用絕對路徑,保證不出錯誤 entry: path.join(__dirname, 'src/index.js'), // mode: 'production', // 出口 output: { // 輸出打包文件名(將index.js以及其依賴的資源打包成bundle.js) filename: 'bundle.[hash:8].js', // 輸出路徑 path: path.join(__dirname, 'dist') }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' }, { test: /\.jsx$/, loader: 'babel-loader' }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(gif|jpg|jpeg|png|svg)$/, use: [ { loader: 'url-loader', options: { limit: 1024, name: '[name]-aaa.[ext]' } } ] }, ] }, plugins: [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: isDev ? '"development"' : '"production"' } }), new VueLoaderPlugin(), new HTMLPlugin() ] } // 若是是開發模式,則進行下列配置 if (isDev) { config.module.rules.push({ test: /\.styl/, use: [ 'style-loader', 'css-loader', { loader: 'postcss-loader', options: { sourceMap: true, } }, 'stylus-loader' ] }); // 控制是否生成,以及如何生成 source map config.devtool = '#cheap-module-eval-source-map'; config.devServer = { // 端口 port: 8080, // 主機 host: '0.0.0.0', // 使webpack錯誤顯示到頁面上 overlay: { error: true, }, // 自動打開瀏覽器 // open: true, // 模塊熱替換,只更新更改的部分 hot: true }; // 添加插件 config.plugins.push( // 熱模塊替換插件 new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ); } else { config.entry = { app: path.join(__dirname, 'src/index.js'), vendor: ['vue'] }; config.output.filename = '[name].[chunkhash:8].js'; config.module.rules.push( { test: /\.styl/, use: ExtractPlugin.extract({ fallback: 'style-loader', use: [ 'css-loader', { loader: 'postcss-loader', options: { sourceMap: true, } }, 'stylus-loader' ] }) }, ); config.plugins.push( new ExtractPlugin('styles.[hash:8].css') ); config.optimization = { splitChunks: { cacheGroups: { commons: { chunks: 'initial', minChunks: 2, maxInitialRequests: 5, minSize: 0 }, vendor: { test: /node_modules/, chunks: 'initial', name: 'vendor', priority: 10, enforce: true } } }, runtimeChunk: true } } module.exports = config;